Upgrade RustPython (#4900)

This commit is contained in:
Micha Reiser 2023-06-08 07:53:14 +02:00 committed by GitHub
parent 4b78141f6b
commit 39a1f3980f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 416 additions and 320 deletions

12
Cargo.lock generated
View file

@ -2030,7 +2030,7 @@ dependencies = [
[[package]] [[package]]
name = "ruff_text_size" name = "ruff_text_size"
version = "0.0.0" version = "0.0.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=0dc8fdf52d146698c5bcf0b842fddc9e398ad8db#0dc8fdf52d146698c5bcf0b842fddc9e398ad8db"
dependencies = [ dependencies = [
"schemars", "schemars",
"serde", "serde",
@ -2108,7 +2108,7 @@ dependencies = [
[[package]] [[package]]
name = "rustpython-ast" name = "rustpython-ast"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=0dc8fdf52d146698c5bcf0b842fddc9e398ad8db#0dc8fdf52d146698c5bcf0b842fddc9e398ad8db"
dependencies = [ dependencies = [
"is-macro", "is-macro",
"num-bigint", "num-bigint",
@ -2119,7 +2119,7 @@ dependencies = [
[[package]] [[package]]
name = "rustpython-format" name = "rustpython-format"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=0dc8fdf52d146698c5bcf0b842fddc9e398ad8db#0dc8fdf52d146698c5bcf0b842fddc9e398ad8db"
dependencies = [ dependencies = [
"bitflags 2.3.1", "bitflags 2.3.1",
"itertools", "itertools",
@ -2131,7 +2131,7 @@ dependencies = [
[[package]] [[package]]
name = "rustpython-literal" name = "rustpython-literal"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=0dc8fdf52d146698c5bcf0b842fddc9e398ad8db#0dc8fdf52d146698c5bcf0b842fddc9e398ad8db"
dependencies = [ dependencies = [
"hexf-parse", "hexf-parse",
"is-macro", "is-macro",
@ -2143,7 +2143,7 @@ dependencies = [
[[package]] [[package]]
name = "rustpython-parser" name = "rustpython-parser"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=0dc8fdf52d146698c5bcf0b842fddc9e398ad8db#0dc8fdf52d146698c5bcf0b842fddc9e398ad8db"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"is-macro", "is-macro",
@ -2166,7 +2166,7 @@ dependencies = [
[[package]] [[package]]
name = "rustpython-parser-core" name = "rustpython-parser-core"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd#7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" source = "git+https://github.com/astral-sh/RustPython-Parser.git?rev=0dc8fdf52d146698c5bcf0b842fddc9e398ad8db#0dc8fdf52d146698c5bcf0b842fddc9e398ad8db"
dependencies = [ dependencies = [
"is-macro", "is-macro",
"ruff_text_size", "ruff_text_size",

View file

@ -1,5 +1,6 @@
[workspace] [workspace]
members = ["crates/*"] members = ["crates/*"]
resolver = "2"
[workspace.package] [workspace.package]
edition = "2021" edition = "2021"
@ -34,11 +35,11 @@ proc-macro2 = { version = "1.0.51" }
quote = { version = "1.0.23" } quote = { version = "1.0.23" }
regex = { version = "1.7.1" } regex = { version = "1.7.1" }
rustc-hash = { version = "1.1.0" } rustc-hash = { version = "1.1.0" }
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" } ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "0dc8fdf52d146698c5bcf0b842fddc9e398ad8db" }
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd", default-features = false, features = ["all-nodes-with-ranges"]} rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "0dc8fdf52d146698c5bcf0b842fddc9e398ad8db", default-features = false, features = ["all-nodes-with-ranges"]}
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" } rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "0dc8fdf52d146698c5bcf0b842fddc9e398ad8db" }
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd" } rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "0dc8fdf52d146698c5bcf0b842fddc9e398ad8db" }
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "7a3eedbf6fb4ea7068a1bf7fe0e97e963ea95ffd", default-features = false, features = ["full-lexer", "all-nodes-with-ranges"] } rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "0dc8fdf52d146698c5bcf0b842fddc9e398ad8db", default-features = false, features = ["full-lexer", "all-nodes-with-ranges"] }
schemars = { version = "0.8.12" } schemars = { version = "0.8.12" }
serde = { version = "1.0.152", features = ["derive"] } serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.93", features = ["preserve_order"] } serde_json = { version = "1.0.93", features = ["preserve_order"] }

View file

@ -1833,8 +1833,8 @@ where
}) => { }) => {
// Visit the decorators and arguments, but avoid the body, which will be // Visit the decorators and arguments, but avoid the body, which will be
// deferred. // deferred.
for expr in decorator_list { for decorator in decorator_list {
self.visit_expr(expr); self.visit_decorator(decorator);
} }
// Function annotations are always evaluated at runtime, unless future annotations // Function annotations are always evaluated at runtime, unless future annotations
@ -1943,8 +1943,8 @@ where
for keyword in keywords { for keyword in keywords {
self.visit_keyword(keyword); self.visit_keyword(keyword);
} }
for expr in decorator_list { for decorator in decorator_list {
self.visit_expr(expr); self.visit_decorator(decorator);
} }
let definition = docstrings::extraction::extract_definition( let definition = docstrings::extraction::extract_definition(

View file

@ -7,7 +7,7 @@ use ruff_python_semantic::model::SemanticModel;
pub(super) fn match_function_def( pub(super) fn match_function_def(
stmt: &Stmt, stmt: &Stmt,
) -> (&str, &Arguments, Option<&Expr>, &[Stmt], &[Expr]) { ) -> (&str, &Arguments, Option<&Expr>, &[Stmt], &[ast::Decorator]) {
match stmt { match stmt {
Stmt::FunctionDef(ast::StmtFunctionDef { Stmt::FunctionDef(ast::StmtFunctionDef {
name, name,

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::{Arguments, Expr}; use rustpython_parser::ast::{Arguments, Decorator};
use ruff_diagnostics::Violation; use ruff_diagnostics::Violation;
@ -61,15 +61,16 @@ impl Violation for BooleanDefaultValueInFunctionDefinition {
pub(crate) fn check_boolean_default_value_in_function_definition( pub(crate) fn check_boolean_default_value_in_function_definition(
checker: &mut Checker, checker: &mut Checker,
name: &str, name: &str,
decorator_list: &[Expr], decorator_list: &[Decorator],
arguments: &Arguments, arguments: &Arguments,
) { ) {
if FUNC_DEF_NAME_ALLOWLIST.contains(&name) { if FUNC_DEF_NAME_ALLOWLIST.contains(&name) {
return; return;
} }
if decorator_list.iter().any(|expr| { if decorator_list.iter().any(|decorator| {
collect_call_path(expr).map_or(false, |call_path| call_path.as_slice() == [name, "setter"]) collect_call_path(&decorator.expression)
.map_or(false, |call_path| call_path.as_slice() == [name, "setter"])
}) { }) {
return; return;
} }

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Arguments, Constant, Expr, Ranged}; use rustpython_parser::ast::{self, Arguments, Constant, Decorator, Expr, Ranged};
use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Violation; use ruff_diagnostics::Violation;
@ -79,15 +79,16 @@ impl Violation for BooleanPositionalArgInFunctionDefinition {
pub(crate) fn check_positional_boolean_in_def( pub(crate) fn check_positional_boolean_in_def(
checker: &mut Checker, checker: &mut Checker,
name: &str, name: &str,
decorator_list: &[Expr], decorator_list: &[Decorator],
arguments: &Arguments, arguments: &Arguments,
) { ) {
if FUNC_DEF_NAME_ALLOWLIST.contains(&name) { if FUNC_DEF_NAME_ALLOWLIST.contains(&name) {
return; return;
} }
if decorator_list.iter().any(|expr| { if decorator_list.iter().any(|decorator| {
collect_call_path(expr).map_or(false, |call_path| call_path.as_slice() == [name, "setter"]) collect_call_path(&decorator.expression)
.map_or(false, |call_path| call_path.as_slice() == [name, "setter"])
}) { }) {
return; return;
} }

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Expr, Ranged}; use rustpython_parser::ast::{self, Decorator, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -26,14 +26,14 @@ fn is_cache_func(model: &SemanticModel, expr: &Expr) -> bool {
} }
/// B019 /// B019
pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Expr]) { pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Decorator]) {
if !checker.semantic_model().scope().kind.is_class() { if !checker.semantic_model().scope().kind.is_class() {
return; return;
} }
for decorator in decorator_list { for decorator in decorator_list {
// TODO(charlie): This should take into account `classmethod-decorators` and // TODO(charlie): This should take into account `classmethod-decorators` and
// `staticmethod-decorators`. // `staticmethod-decorators`.
if let Expr::Name(ast::ExprName { id, .. }) = decorator { if let Expr::Name(ast::ExprName { id, .. }) = &decorator.expression {
if id == "classmethod" || id == "staticmethod" { if id == "classmethod" || id == "staticmethod" {
return; return;
} }
@ -42,9 +42,9 @@ pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Ex
for decorator in decorator_list { for decorator in decorator_list {
if is_cache_func( if is_cache_func(
checker.semantic_model(), checker.semantic_model(),
match decorator { match &decorator.expression {
Expr::Call(ast::ExprCall { func, .. }) => func, Expr::Call(ast::ExprCall { func, .. }) => func,
_ => decorator, _ => &decorator.expression,
}, },
) { ) {
checker checker

View file

@ -1,81 +1,81 @@
--- ---
source: crates/ruff/src/rules/flake8_bugbear/mod.rs source: crates/ruff/src/rules/flake8_bugbear/mod.rs
--- ---
B019.py:78:6: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks B019.py:78:5: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks
| |
78 | # Remaining methods should emit B019 78 | # Remaining methods should emit B019
79 | @functools.cache 79 | @functools.cache
| ^^^^^^^^^^^^^^^ B019 | ^^^^^^^^^^^^^^^^ B019
80 | def cached_instance_method(self, y): 80 | def cached_instance_method(self, y):
81 | ... 81 | ...
| |
B019.py:82:6: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks B019.py:82:5: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks
| |
82 | ... 82 | ...
83 | 83 |
84 | @cache 84 | @cache
| ^^^^^ B019 | ^^^^^^ B019
85 | def another_cached_instance_method(self, y): 85 | def another_cached_instance_method(self, y):
86 | ... 86 | ...
| |
B019.py:86:6: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks B019.py:86:5: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks
| |
86 | ... 86 | ...
87 | 87 |
88 | @functools.cache() 88 | @functools.cache()
| ^^^^^^^^^^^^^^^^^ B019 | ^^^^^^^^^^^^^^^^^^ B019
89 | def called_cached_instance_method(self, y): 89 | def called_cached_instance_method(self, y):
90 | ... 90 | ...
| |
B019.py:90:6: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks B019.py:90:5: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks
| |
90 | ... 90 | ...
91 | 91 |
92 | @cache() 92 | @cache()
| ^^^^^^^ B019 | ^^^^^^^^ B019
93 | def another_called_cached_instance_method(self, y): 93 | def another_called_cached_instance_method(self, y):
94 | ... 94 | ...
| |
B019.py:94:6: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks B019.py:94:5: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks
| |
94 | ... 94 | ...
95 | 95 |
96 | @functools.lru_cache 96 | @functools.lru_cache
| ^^^^^^^^^^^^^^^^^^^ B019 | ^^^^^^^^^^^^^^^^^^^^ B019
97 | def lru_cached_instance_method(self, y): 97 | def lru_cached_instance_method(self, y):
98 | ... 98 | ...
| |
B019.py:98:6: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks B019.py:98:5: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks
| |
98 | ... 98 | ...
99 | 99 |
100 | @lru_cache 100 | @lru_cache
| ^^^^^^^^^ B019 | ^^^^^^^^^^ B019
101 | def another_lru_cached_instance_method(self, y): 101 | def another_lru_cached_instance_method(self, y):
102 | ... 102 | ...
| |
B019.py:102:6: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks B019.py:102:5: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks
| |
102 | ... 102 | ...
103 | 103 |
104 | @functools.lru_cache() 104 | @functools.lru_cache()
| ^^^^^^^^^^^^^^^^^^^^^ B019 | ^^^^^^^^^^^^^^^^^^^^^^ B019
105 | def called_lru_cached_instance_method(self, y): 105 | def called_lru_cached_instance_method(self, y):
106 | ... 106 | ...
| |
B019.py:106:6: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks B019.py:106:5: B019 Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks
| |
106 | ... 106 | ...
107 | 107 |
108 | @lru_cache() 108 | @lru_cache()
| ^^^^^^^^^^^ B019 | ^^^^^^^^^^^^ B019
109 | def another_called_lru_cached_instance_method(self, y): 109 | def another_called_lru_cached_instance_method(self, y):
110 | ... 110 | ...
| |

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Expr, Ranged}; use rustpython_parser::ast::{self, Decorator, Expr, Ranged};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -49,7 +49,7 @@ impl Violation for DjangoNonLeadingReceiverDecorator {
/// DJ013 /// DJ013
pub(crate) fn non_leading_receiver_decorator<'a, F>( pub(crate) fn non_leading_receiver_decorator<'a, F>(
decorator_list: &'a [Expr], decorator_list: &'a [Decorator],
resolve_call_path: F, resolve_call_path: F,
) -> Vec<Diagnostic> ) -> Vec<Diagnostic>
where where
@ -58,7 +58,7 @@ where
let mut diagnostics = vec![]; let mut diagnostics = vec![];
let mut seen_receiver = false; let mut seen_receiver = false;
for (i, decorator) in decorator_list.iter().enumerate() { for (i, decorator) in decorator_list.iter().enumerate() {
let is_receiver = match decorator { let is_receiver = match &decorator.expression {
Expr::Call(ast::ExprCall { func, .. }) => resolve_call_path(func) Expr::Call(ast::ExprCall { func, .. }) => resolve_call_path(func)
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["django", "dispatch", "receiver"] call_path.as_slice() == ["django", "dispatch", "receiver"]

View file

@ -1,21 +1,21 @@
--- ---
source: crates/ruff/src/rules/flake8_django/mod.rs source: crates/ruff/src/rules/flake8_django/mod.rs
--- ---
DJ013.py:15:2: DJ013 `@receiver` decorator must be on top of all the other decorators DJ013.py:15:1: DJ013 `@receiver` decorator must be on top of all the other decorators
| |
15 | @test_decorator 15 | @test_decorator
16 | @receiver(pre_save, sender=MyModel) 16 | @receiver(pre_save, sender=MyModel)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DJ013 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DJ013
17 | def incorrect_pre_save_handler(): 17 | def incorrect_pre_save_handler():
18 | pass 18 | pass
| |
DJ013.py:35:2: DJ013 `@receiver` decorator must be on top of all the other decorators DJ013.py:35:1: DJ013 `@receiver` decorator must be on top of all the other decorators
| |
35 | @receiver(pre_save, sender=MyModel) 35 | @receiver(pre_save, sender=MyModel)
36 | @test_decorator 36 | @test_decorator
37 | @receiver(pre_save, sender=MyModel) 37 | @receiver(pre_save, sender=MyModel)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DJ013 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DJ013
38 | def incorrect_multiple(): 38 | def incorrect_multiple():
39 | pass 39 | pass
| |

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Arguments, Expr, Stmt}; use rustpython_parser::ast::{self, Arguments, Decorator, Expr, Stmt};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -114,7 +114,7 @@ pub(crate) fn non_self_return_type(
checker: &mut Checker, checker: &mut Checker,
stmt: &Stmt, stmt: &Stmt,
name: &str, name: &str,
decorator_list: &[Expr], decorator_list: &[Decorator],
returns: Option<&Expr>, returns: Option<&Expr>,
args: &Arguments, args: &Arguments,
async_: bool, async_: bool,

View file

@ -9,6 +9,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::collect_call_path; use ruff_python_ast::call_path::collect_call_path;
use ruff_python_ast::helpers::collect_arg_names; use ruff_python_ast::helpers::collect_arg_names;
use ruff_python_ast::prelude::Decorator;
use ruff_python_ast::source_code::Locator; use ruff_python_ast::source_code::Locator;
use ruff_python_ast::visitor::Visitor; use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{helpers, visitor}; use ruff_python_ast::{helpers, visitor};
@ -243,7 +244,10 @@ where
} }
} }
fn get_fixture_decorator<'a>(model: &SemanticModel, decorators: &'a [Expr]) -> Option<&'a Expr> { fn get_fixture_decorator<'a>(
model: &SemanticModel,
decorators: &'a [Decorator],
) -> Option<&'a Decorator> {
decorators.iter().find(|decorator| { decorators.iter().find(|decorator| {
is_pytest_fixture(model, decorator) || is_pytest_yield_fixture(model, decorator) is_pytest_fixture(model, decorator) || is_pytest_yield_fixture(model, decorator)
}) })
@ -251,7 +255,7 @@ fn get_fixture_decorator<'a>(model: &SemanticModel, decorators: &'a [Expr]) -> O
fn pytest_fixture_parentheses( fn pytest_fixture_parentheses(
checker: &mut Checker, checker: &mut Checker,
decorator: &Expr, decorator: &Decorator,
fix: Fix, fix: Fix,
expected: Parentheses, expected: Parentheses,
actual: Parentheses, actual: Parentheses,
@ -277,8 +281,8 @@ pub(crate) fn fix_extraneous_scope_function(
} }
/// PT001, PT002, PT003 /// PT001, PT002, PT003
fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &Expr) { fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &Decorator) {
match decorator { match &decorator.expression {
Expr::Call(ast::ExprCall { Expr::Call(ast::ExprCall {
func, func,
args, args,
@ -431,7 +435,7 @@ fn check_test_function_args(checker: &mut Checker, args: &Arguments) {
} }
/// PT020 /// PT020
fn check_fixture_decorator_name(checker: &mut Checker, decorator: &Expr) { fn check_fixture_decorator_name(checker: &mut Checker, decorator: &Decorator) {
if is_pytest_yield_fixture(checker.semantic_model(), decorator) { if is_pytest_yield_fixture(checker.semantic_model(), decorator) {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
PytestDeprecatedYieldFixture, PytestDeprecatedYieldFixture,
@ -461,7 +465,7 @@ fn check_fixture_addfinalizer(checker: &mut Checker, args: &Arguments, body: &[S
} }
/// PT024, PT025 /// PT024, PT025
fn check_fixture_marks(checker: &mut Checker, decorators: &[Expr]) { fn check_fixture_marks(checker: &mut Checker, decorators: &[Decorator]) {
for (expr, call_path) in get_mark_decorators(decorators) { for (expr, call_path) in get_mark_decorators(decorators) {
let name = call_path.last().expect("Expected a mark name"); let name = call_path.last().expect("Expected a mark name");
if checker.enabled(Rule::PytestUnnecessaryAsyncioMarkOnFixture) { if checker.enabled(Rule::PytestUnnecessaryAsyncioMarkOnFixture) {
@ -497,7 +501,7 @@ pub(crate) fn fixture(
stmt: &Stmt, stmt: &Stmt,
name: &str, name: &str,
args: &Arguments, args: &Arguments,
decorators: &[Expr], decorators: &[Decorator],
body: &[Stmt], body: &[Stmt],
) { ) {
let decorator = get_fixture_decorator(checker.semantic_model(), decorators); let decorator = get_fixture_decorator(checker.semantic_model(), decorators);

View file

@ -1,12 +1,14 @@
use rustpython_parser::ast::{self, Constant, Expr, Keyword}; use rustpython_parser::ast::{self, Constant, Decorator, Expr, Keyword};
use ruff_python_ast::call_path::{collect_call_path, CallPath}; use ruff_python_ast::call_path::{collect_call_path, CallPath};
use ruff_python_ast::helpers::map_callable; use ruff_python_ast::helpers::map_callable;
use ruff_python_semantic::model::SemanticModel; use ruff_python_semantic::model::SemanticModel;
pub(super) fn get_mark_decorators(decorators: &[Expr]) -> impl Iterator<Item = (&Expr, CallPath)> { pub(super) fn get_mark_decorators(
decorators: &[Decorator],
) -> impl Iterator<Item = (&Decorator, CallPath)> {
decorators.iter().filter_map(|decorator| { decorators.iter().filter_map(|decorator| {
let Some(call_path) = collect_call_path(map_callable(decorator)) else { let Some(call_path) = collect_call_path(map_callable(&decorator.expression)) else {
return None; return None;
}; };
if call_path.len() > 2 && call_path.as_slice()[..2] == ["pytest", "mark"] { if call_path.len() > 2 && call_path.as_slice()[..2] == ["pytest", "mark"] {
@ -23,25 +25,25 @@ pub(super) fn is_pytest_fail(model: &SemanticModel, call: &Expr) -> bool {
}) })
} }
pub(super) fn is_pytest_fixture(model: &SemanticModel, decorator: &Expr) -> bool { pub(super) fn is_pytest_fixture(model: &SemanticModel, decorator: &Decorator) -> bool {
model model
.resolve_call_path(map_callable(decorator)) .resolve_call_path(map_callable(&decorator.expression))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["pytest", "fixture"] call_path.as_slice() == ["pytest", "fixture"]
}) })
} }
pub(super) fn is_pytest_yield_fixture(model: &SemanticModel, decorator: &Expr) -> bool { pub(super) fn is_pytest_yield_fixture(model: &SemanticModel, decorator: &Decorator) -> bool {
model model
.resolve_call_path(map_callable(decorator)) .resolve_call_path(map_callable(&decorator.expression))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["pytest", "yield_fixture"] call_path.as_slice() == ["pytest", "yield_fixture"]
}) })
} }
pub(super) fn is_pytest_parametrize(model: &SemanticModel, decorator: &Expr) -> bool { pub(super) fn is_pytest_parametrize(model: &SemanticModel, decorator: &Decorator) -> bool {
model model
.resolve_call_path(map_callable(decorator)) .resolve_call_path(map_callable(&decorator.expression))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["pytest", "mark", "parametrize"] call_path.as_slice() == ["pytest", "mark", "parametrize"]
}) })

View file

@ -1,5 +1,4 @@
use ruff_text_size::TextSize; use rustpython_parser::ast::{self, Decorator, Expr, Ranged};
use rustpython_parser::ast::{self, Expr, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -52,7 +51,7 @@ impl AlwaysAutofixableViolation for PytestUseFixturesWithoutParameters {
fn pytest_mark_parentheses( fn pytest_mark_parentheses(
checker: &mut Checker, checker: &mut Checker,
decorator: &Expr, decorator: &Decorator,
call_path: &CallPath, call_path: &CallPath,
fix: Fix, fix: Fix,
preferred: &str, preferred: &str,
@ -72,8 +71,8 @@ fn pytest_mark_parentheses(
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
fn check_mark_parentheses(checker: &mut Checker, decorator: &Expr, call_path: &CallPath) { fn check_mark_parentheses(checker: &mut Checker, decorator: &Decorator, call_path: &CallPath) {
match decorator { match &decorator.expression {
Expr::Call(ast::ExprCall { Expr::Call(ast::ExprCall {
func, func,
args, args,
@ -99,14 +98,14 @@ fn check_mark_parentheses(checker: &mut Checker, decorator: &Expr, call_path: &C
} }
} }
fn check_useless_usefixtures(checker: &mut Checker, decorator: &Expr, call_path: &CallPath) { fn check_useless_usefixtures(checker: &mut Checker, decorator: &Decorator, call_path: &CallPath) {
if *call_path.last().unwrap() != "usefixtures" { if *call_path.last().unwrap() != "usefixtures" {
return; return;
} }
let mut has_parameters = false; let mut has_parameters = false;
if let Expr::Call(ast::ExprCall { args, keywords, .. }) = decorator { if let Expr::Call(ast::ExprCall { args, keywords, .. }) = &decorator.expression {
if !args.is_empty() || !keywords.is_empty() { if !args.is_empty() || !keywords.is_empty() {
has_parameters = true; has_parameters = true;
} }
@ -116,24 +115,22 @@ fn check_useless_usefixtures(checker: &mut Checker, decorator: &Expr, call_path:
let mut diagnostic = Diagnostic::new(PytestUseFixturesWithoutParameters, decorator.range()); let mut diagnostic = Diagnostic::new(PytestUseFixturesWithoutParameters, decorator.range());
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_deletion( diagnostic.set_fix(Fix::unspecified(Edit::range_deletion(decorator.range())));
decorator.range().sub_start(TextSize::from(1)),
)));
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }
pub(crate) fn marks(checker: &mut Checker, decorators: &[Expr]) { pub(crate) fn marks(checker: &mut Checker, decorators: &[Decorator]) {
let enforce_parentheses = checker.enabled(Rule::PytestIncorrectMarkParenthesesStyle); let enforce_parentheses = checker.enabled(Rule::PytestIncorrectMarkParenthesesStyle);
let enforce_useless_usefixtures = checker.enabled(Rule::PytestUseFixturesWithoutParameters); let enforce_useless_usefixtures = checker.enabled(Rule::PytestUseFixturesWithoutParameters);
for (expr, call_path) in get_mark_decorators(decorators) { for (decorator, call_path) in get_mark_decorators(decorators) {
if enforce_parentheses { if enforce_parentheses {
check_mark_parentheses(checker, expr, &call_path); check_mark_parentheses(checker, decorator, &call_path);
} }
if enforce_useless_usefixtures { if enforce_useless_usefixtures {
check_useless_usefixtures(checker, expr, &call_path); check_useless_usefixtures(checker, decorator, &call_path);
} }
} }
} }

View file

@ -1,5 +1,5 @@
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use rustpython_parser::ast::{self, Constant, Expr, ExprContext, Ranged}; use rustpython_parser::ast::{self, Constant, Decorator, Expr, ExprContext, Ranged};
use rustpython_parser::{lexer, Mode, Tok}; use rustpython_parser::{lexer, Mode, Tok};
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
@ -94,7 +94,7 @@ fn elts_to_csv(elts: &[Expr], generator: Generator) -> Option<String> {
/// ``` /// ```
/// ///
/// This method assumes that the first argument is a string. /// This method assumes that the first argument is a string.
fn get_parametrize_name_range(decorator: &Expr, expr: &Expr, locator: &Locator) -> TextRange { fn get_parametrize_name_range(decorator: &Decorator, expr: &Expr, locator: &Locator) -> TextRange {
let mut locations = Vec::new(); let mut locations = Vec::new();
let mut implicit_concat = None; let mut implicit_concat = None;
@ -128,7 +128,7 @@ fn get_parametrize_name_range(decorator: &Expr, expr: &Expr, locator: &Locator)
} }
/// PT006 /// PT006
fn check_names(checker: &mut Checker, decorator: &Expr, expr: &Expr) { fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
let names_type = checker.settings.flake8_pytest_style.parametrize_names_type; let names_type = checker.settings.flake8_pytest_style.parametrize_names_type;
match expr { match expr {
@ -417,10 +417,10 @@ fn handle_value_rows(
} }
} }
pub(crate) fn parametrize(checker: &mut Checker, decorators: &[Expr]) { pub(crate) fn parametrize(checker: &mut Checker, decorators: &[Decorator]) {
for decorator in decorators { for decorator in decorators {
if is_pytest_parametrize(checker.semantic_model(), decorator) { if is_pytest_parametrize(checker.semantic_model(), decorator) {
if let Expr::Call(ast::ExprCall { args, .. }) = decorator { if let Expr::Call(ast::ExprCall { args, .. }) = &decorator.expression {
if checker.enabled(Rule::PytestParametrizeNamesWrongType) { if checker.enabled(Rule::PytestParametrizeNamesWrongType) {
if let Some(names) = args.get(0) { if let Some(names) = args.get(0) {
check_names(checker, decorator, names); check_names(checker, decorator, names);

View file

@ -1,10 +1,10 @@
--- ---
source: crates/ruff/src/rules/flake8_pytest_style/mod.rs source: crates/ruff/src/rules/flake8_pytest_style/mod.rs
--- ---
PT001.py:9:2: PT001 [*] Use `@pytest.fixture()` over `@pytest.fixture` PT001.py:9:1: PT001 [*] Use `@pytest.fixture()` over `@pytest.fixture`
| |
9 | @pytest.fixture 9 | @pytest.fixture
| ^^^^^^^^^^^^^^ PT001 | ^^^^^^^^^^^^^^^ PT001
10 | def no_parentheses(): 10 | def no_parentheses():
11 | return 42 11 | return 42
| |
@ -20,10 +20,10 @@ PT001.py:9:2: PT001 [*] Use `@pytest.fixture()` over `@pytest.fixture`
11 11 | return 42 11 11 | return 42
12 12 | 12 12 |
PT001.py:34:2: PT001 [*] Use `@pytest.fixture()` over `@pytest.fixture` PT001.py:34:1: PT001 [*] Use `@pytest.fixture()` over `@pytest.fixture`
| |
34 | @fixture 34 | @fixture
| ^^^^^^^ PT001 | ^^^^^^^^ PT001
35 | def imported_from_no_parentheses(): 35 | def imported_from_no_parentheses():
36 | return 42 36 | return 42
| |
@ -39,10 +39,10 @@ PT001.py:34:2: PT001 [*] Use `@pytest.fixture()` over `@pytest.fixture`
36 36 | return 42 36 36 | return 42
37 37 | 37 37 |
PT001.py:59:2: PT001 [*] Use `@pytest.fixture()` over `@pytest.fixture` PT001.py:59:1: PT001 [*] Use `@pytest.fixture()` over `@pytest.fixture`
| |
59 | @aliased 59 | @aliased
| ^^^^^^^ PT001 | ^^^^^^^^ PT001
60 | def aliased_no_parentheses(): 60 | def aliased_no_parentheses():
61 | return 42 61 | return 42
| |

View file

@ -1,10 +1,10 @@
--- ---
source: crates/ruff/src/rules/flake8_pytest_style/mod.rs source: crates/ruff/src/rules/flake8_pytest_style/mod.rs
--- ---
PT001.py:14:2: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()` PT001.py:14:1: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()`
| |
14 | @pytest.fixture() 14 | @pytest.fixture()
| ^^^^^^^^^^^^^^^^ PT001 | ^^^^^^^^^^^^^^^^^ PT001
15 | def parentheses_no_params(): 15 | def parentheses_no_params():
16 | return 42 16 | return 42
| |
@ -20,10 +20,9 @@ PT001.py:14:2: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()`
16 16 | return 42 16 16 | return 42
17 17 | 17 17 |
PT001.py:24:2: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()` PT001.py:24:1: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()`
| |
24 | @pytest.fixture( 24 | / @pytest.fixture(
| __^
25 | | 25 | |
26 | | ) 26 | | )
| |_^ PT001 | |_^ PT001
@ -44,10 +43,10 @@ PT001.py:24:2: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()`
28 26 | return 42 28 26 | return 42
29 27 | 29 27 |
PT001.py:39:2: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()` PT001.py:39:1: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()`
| |
39 | @fixture() 39 | @fixture()
| ^^^^^^^^^ PT001 | ^^^^^^^^^^ PT001
40 | def imported_from_parentheses_no_params(): 40 | def imported_from_parentheses_no_params():
41 | return 42 41 | return 42
| |
@ -63,10 +62,9 @@ PT001.py:39:2: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()`
41 41 | return 42 41 41 | return 42
42 42 | 42 42 |
PT001.py:49:2: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()` PT001.py:49:1: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()`
| |
49 | @fixture( 49 | / @fixture(
| __^
50 | | 50 | |
51 | | ) 51 | | )
| |_^ PT001 | |_^ PT001
@ -87,10 +85,10 @@ PT001.py:49:2: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()`
53 51 | return 42 53 51 | return 42
54 52 | 54 52 |
PT001.py:64:2: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()` PT001.py:64:1: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()`
| |
64 | @aliased() 64 | @aliased()
| ^^^^^^^^^ PT001 | ^^^^^^^^^^ PT001
65 | def aliased_parentheses_no_params(): 65 | def aliased_parentheses_no_params():
66 | return 42 66 | return 42
| |
@ -106,10 +104,9 @@ PT001.py:64:2: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()`
66 66 | return 42 66 66 | return 42
67 67 | 67 67 |
PT001.py:74:2: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()` PT001.py:74:1: PT001 [*] Use `@pytest.fixture` over `@pytest.fixture()`
| |
74 | @aliased( 74 | / @aliased(
| __^
75 | | 75 | |
76 | | ) 76 | | )
| |_^ PT001 | |_^ PT001

View file

@ -1,18 +1,18 @@
--- ---
source: crates/ruff/src/rules/flake8_pytest_style/mod.rs source: crates/ruff/src/rules/flake8_pytest_style/mod.rs
--- ---
PT002.py:14:2: PT002 Configuration for fixture `my_fixture` specified via positional args, use kwargs PT002.py:14:1: PT002 Configuration for fixture `my_fixture` specified via positional args, use kwargs
| |
14 | @pytest.fixture("module") 14 | @pytest.fixture("module")
| ^^^^^^^^^^^^^^^^^^^^^^^^ PT002 | ^^^^^^^^^^^^^^^^^^^^^^^^^ PT002
15 | def my_fixture(): # Error only args 15 | def my_fixture(): # Error only args
16 | return 0 16 | return 0
| |
PT002.py:19:2: PT002 Configuration for fixture `my_fixture` specified via positional args, use kwargs PT002.py:19:1: PT002 Configuration for fixture `my_fixture` specified via positional args, use kwargs
| |
19 | @pytest.fixture("module", autouse=True) 19 | @pytest.fixture("module", autouse=True)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT002 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT002
20 | def my_fixture(): # Error mixed 20 | def my_fixture(): # Error mixed
21 | return 0 21 | return 0
| |

View file

@ -1,18 +1,18 @@
--- ---
source: crates/ruff/src/rules/flake8_pytest_style/mod.rs source: crates/ruff/src/rules/flake8_pytest_style/mod.rs
--- ---
PT020.py:14:2: PT020 `@pytest.yield_fixture` is deprecated, use `@pytest.fixture` PT020.py:14:1: PT020 `@pytest.yield_fixture` is deprecated, use `@pytest.fixture`
| |
14 | @pytest.yield_fixture() 14 | @pytest.yield_fixture()
| ^^^^^^^^^^^^^^^^^^^^^^ PT020 | ^^^^^^^^^^^^^^^^^^^^^^^ PT020
15 | def error_without_parens(): 15 | def error_without_parens():
16 | return 0 16 | return 0
| |
PT020.py:19:2: PT020 `@pytest.yield_fixture` is deprecated, use `@pytest.fixture` PT020.py:19:1: PT020 `@pytest.yield_fixture` is deprecated, use `@pytest.fixture`
| |
19 | @pytest.yield_fixture 19 | @pytest.yield_fixture
| ^^^^^^^^^^^^^^^^^^^^ PT020 | ^^^^^^^^^^^^^^^^^^^^^ PT020
20 | def error_with_parens(): 20 | def error_with_parens():
21 | return 0 21 | return 0
| |

View file

@ -1,10 +1,10 @@
--- ---
source: crates/ruff/src/rules/flake8_pytest_style/mod.rs source: crates/ruff/src/rules/flake8_pytest_style/mod.rs
--- ---
PT023.py:12:2: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo` PT023.py:12:1: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
| |
12 | @pytest.mark.foo 12 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^ PT023 | ^^^^^^^^^^^^^^^^ PT023
13 | def test_something(): 13 | def test_something():
14 | pass 14 | pass
| |
@ -20,10 +20,10 @@ PT023.py:12:2: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
14 14 | pass 14 14 | pass
15 15 | 15 15 |
PT023.py:17:2: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo` PT023.py:17:1: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
| |
17 | @pytest.mark.foo 17 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^ PT023 | ^^^^^^^^^^^^^^^^ PT023
18 | class TestClass: 18 | class TestClass:
19 | def test_something(): 19 | def test_something():
| |
@ -39,11 +39,11 @@ PT023.py:17:2: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
19 19 | def test_something(): 19 19 | def test_something():
20 20 | pass 20 20 | pass
PT023.py:24:6: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo` PT023.py:24:5: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
| |
24 | class TestClass: 24 | class TestClass:
25 | @pytest.mark.foo 25 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^ PT023 | ^^^^^^^^^^^^^^^^ PT023
26 | def test_something(): 26 | def test_something():
27 | pass 27 | pass
| |
@ -59,11 +59,11 @@ PT023.py:24:6: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
26 26 | pass 26 26 | pass
27 27 | 27 27 |
PT023.py:30:6: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo` PT023.py:30:5: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
| |
30 | class TestClass: 30 | class TestClass:
31 | @pytest.mark.foo 31 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^ PT023 | ^^^^^^^^^^^^^^^^ PT023
32 | class TestNestedClass: 32 | class TestNestedClass:
33 | def test_something(): 33 | def test_something():
| |
@ -79,12 +79,12 @@ PT023.py:30:6: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
32 32 | def test_something(): 32 32 | def test_something():
33 33 | pass 33 33 | pass
PT023.py:38:10: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo` PT023.py:38:9: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
| |
38 | class TestClass: 38 | class TestClass:
39 | class TestNestedClass: 39 | class TestNestedClass:
40 | @pytest.mark.foo 40 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^ PT023 | ^^^^^^^^^^^^^^^^ PT023
41 | def test_something(): 41 | def test_something():
42 | pass 42 | pass
| |

View file

@ -1,10 +1,10 @@
--- ---
source: crates/ruff/src/rules/flake8_pytest_style/mod.rs source: crates/ruff/src/rules/flake8_pytest_style/mod.rs
--- ---
PT023.py:46:2: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()` PT023.py:46:1: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
| |
46 | @pytest.mark.foo() 46 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^ PT023 | ^^^^^^^^^^^^^^^^^^ PT023
47 | def test_something(): 47 | def test_something():
48 | pass 48 | pass
| |
@ -20,10 +20,10 @@ PT023.py:46:2: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
48 48 | pass 48 48 | pass
49 49 | 49 49 |
PT023.py:51:2: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()` PT023.py:51:1: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
| |
51 | @pytest.mark.foo() 51 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^ PT023 | ^^^^^^^^^^^^^^^^^^ PT023
52 | class TestClass: 52 | class TestClass:
53 | def test_something(): 53 | def test_something():
| |
@ -39,11 +39,11 @@ PT023.py:51:2: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
53 53 | def test_something(): 53 53 | def test_something():
54 54 | pass 54 54 | pass
PT023.py:58:6: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()` PT023.py:58:5: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
| |
58 | class TestClass: 58 | class TestClass:
59 | @pytest.mark.foo() 59 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^ PT023 | ^^^^^^^^^^^^^^^^^^ PT023
60 | def test_something(): 60 | def test_something():
61 | pass 61 | pass
| |
@ -59,11 +59,11 @@ PT023.py:58:6: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
60 60 | pass 60 60 | pass
61 61 | 61 61 |
PT023.py:64:6: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()` PT023.py:64:5: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
| |
64 | class TestClass: 64 | class TestClass:
65 | @pytest.mark.foo() 65 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^ PT023 | ^^^^^^^^^^^^^^^^^^ PT023
66 | class TestNestedClass: 66 | class TestNestedClass:
67 | def test_something(): 67 | def test_something():
| |
@ -79,12 +79,12 @@ PT023.py:64:6: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
66 66 | def test_something(): 66 66 | def test_something():
67 67 | pass 67 67 | pass
PT023.py:72:10: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()` PT023.py:72:9: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
| |
72 | class TestClass: 72 | class TestClass:
73 | class TestNestedClass: 73 | class TestNestedClass:
74 | @pytest.mark.foo() 74 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^ PT023 | ^^^^^^^^^^^^^^^^^^ PT023
75 | def test_something(): 75 | def test_something():
76 | pass 76 | pass
| |

View file

@ -1,10 +1,10 @@
--- ---
source: crates/ruff/src/rules/flake8_pytest_style/mod.rs source: crates/ruff/src/rules/flake8_pytest_style/mod.rs
--- ---
PT024.py:14:2: PT024 [*] `pytest.mark.asyncio` is unnecessary for fixtures PT024.py:14:1: PT024 [*] `pytest.mark.asyncio` is unnecessary for fixtures
| |
14 | @pytest.mark.asyncio() 14 | @pytest.mark.asyncio()
| ^^^^^^^^^^^^^^^^^^^^^ PT024 | ^^^^^^^^^^^^^^^^^^^^^^ PT024
15 | @pytest.fixture() 15 | @pytest.fixture()
16 | async def my_fixture(): # Error before 16 | async def my_fixture(): # Error before
| |
@ -19,10 +19,10 @@ PT024.py:14:2: PT024 [*] `pytest.mark.asyncio` is unnecessary for fixtures
16 15 | async def my_fixture(): # Error before 16 15 | async def my_fixture(): # Error before
17 16 | return 0 17 16 | return 0
PT024.py:20:2: PT024 [*] `pytest.mark.asyncio` is unnecessary for fixtures PT024.py:20:1: PT024 [*] `pytest.mark.asyncio` is unnecessary for fixtures
| |
20 | @pytest.mark.asyncio 20 | @pytest.mark.asyncio
| ^^^^^^^^^^^^^^^^^^^ PT024 | ^^^^^^^^^^^^^^^^^^^^ PT024
21 | @pytest.fixture() 21 | @pytest.fixture()
22 | async def my_fixture(): # Error before no parens 22 | async def my_fixture(): # Error before no parens
| |
@ -37,11 +37,11 @@ PT024.py:20:2: PT024 [*] `pytest.mark.asyncio` is unnecessary for fixtures
22 21 | async def my_fixture(): # Error before no parens 22 21 | async def my_fixture(): # Error before no parens
23 22 | return 0 23 22 | return 0
PT024.py:27:2: PT024 [*] `pytest.mark.asyncio` is unnecessary for fixtures PT024.py:27:1: PT024 [*] `pytest.mark.asyncio` is unnecessary for fixtures
| |
27 | @pytest.fixture() 27 | @pytest.fixture()
28 | @pytest.mark.asyncio() 28 | @pytest.mark.asyncio()
| ^^^^^^^^^^^^^^^^^^^^^ PT024 | ^^^^^^^^^^^^^^^^^^^^^^ PT024
29 | async def my_fixture(): # Error after 29 | async def my_fixture(): # Error after
30 | return 0 30 | return 0
| |
@ -56,11 +56,11 @@ PT024.py:27:2: PT024 [*] `pytest.mark.asyncio` is unnecessary for fixtures
29 28 | return 0 29 28 | return 0
30 29 | 30 29 |
PT024.py:33:2: PT024 [*] `pytest.mark.asyncio` is unnecessary for fixtures PT024.py:33:1: PT024 [*] `pytest.mark.asyncio` is unnecessary for fixtures
| |
33 | @pytest.fixture() 33 | @pytest.fixture()
34 | @pytest.mark.asyncio 34 | @pytest.mark.asyncio
| ^^^^^^^^^^^^^^^^^^^ PT024 | ^^^^^^^^^^^^^^^^^^^^ PT024
35 | async def my_fixture(): # Error after no parens 35 | async def my_fixture(): # Error after no parens
36 | return 0 36 | return 0
| |

View file

@ -1,10 +1,10 @@
--- ---
source: crates/ruff/src/rules/flake8_pytest_style/mod.rs source: crates/ruff/src/rules/flake8_pytest_style/mod.rs
--- ---
PT025.py:9:2: PT025 [*] `pytest.mark.usefixtures` has no effect on fixtures PT025.py:9:1: PT025 [*] `pytest.mark.usefixtures` has no effect on fixtures
| |
9 | @pytest.mark.usefixtures("a") 9 | @pytest.mark.usefixtures("a")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT025 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT025
10 | @pytest.fixture() 10 | @pytest.fixture()
11 | def my_fixture(): # Error before 11 | def my_fixture(): # Error before
| |
@ -19,11 +19,11 @@ PT025.py:9:2: PT025 [*] `pytest.mark.usefixtures` has no effect on fixtures
11 10 | def my_fixture(): # Error before 11 10 | def my_fixture(): # Error before
12 11 | return 0 12 11 | return 0
PT025.py:16:2: PT025 [*] `pytest.mark.usefixtures` has no effect on fixtures PT025.py:16:1: PT025 [*] `pytest.mark.usefixtures` has no effect on fixtures
| |
16 | @pytest.fixture() 16 | @pytest.fixture()
17 | @pytest.mark.usefixtures("a") 17 | @pytest.mark.usefixtures("a")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT025 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT025
18 | def my_fixture(): # Error after 18 | def my_fixture(): # Error after
19 | return 0 19 | return 0
| |

View file

@ -1,10 +1,10 @@
--- ---
source: crates/ruff/src/rules/flake8_pytest_style/mod.rs source: crates/ruff/src/rules/flake8_pytest_style/mod.rs
--- ---
PT026.py:19:2: PT026 [*] Useless `pytest.mark.usefixtures` without parameters PT026.py:19:1: PT026 [*] Useless `pytest.mark.usefixtures` without parameters
| |
19 | @pytest.mark.usefixtures() 19 | @pytest.mark.usefixtures()
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PT026 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ PT026
20 | def test_error_with_parens(): 20 | def test_error_with_parens():
21 | pass 21 | pass
| |
@ -20,10 +20,10 @@ PT026.py:19:2: PT026 [*] Useless `pytest.mark.usefixtures` without parameters
21 21 | pass 21 21 | pass
22 22 | 22 22 |
PT026.py:24:2: PT026 [*] Useless `pytest.mark.usefixtures` without parameters PT026.py:24:1: PT026 [*] Useless `pytest.mark.usefixtures` without parameters
| |
24 | @pytest.mark.usefixtures 24 | @pytest.mark.usefixtures
| ^^^^^^^^^^^^^^^^^^^^^^^ PT026 | ^^^^^^^^^^^^^^^^^^^^^^^^ PT026
25 | def test_error_no_parens(): 25 | def test_error_no_parens():
26 | pass 26 | pass
| |

View file

@ -87,8 +87,8 @@ impl<'a> Visitor<'a> for ReturnVisitor<'a> {
.push(stmt.start()); .push(stmt.start());
// Don't recurse into the body, but visit the decorators, etc. // Don't recurse into the body, but visit the decorators, etc.
for expr in decorator_list { for decorator in decorator_list {
visitor::walk_expr(self, expr); visitor::walk_decorator(self, decorator);
} }
} }
Stmt::FunctionDef(ast::StmtFunctionDef { Stmt::FunctionDef(ast::StmtFunctionDef {
@ -113,8 +113,8 @@ impl<'a> Visitor<'a> for ReturnVisitor<'a> {
.push(stmt.start()); .push(stmt.start());
// Don't recurse into the body, but visit the decorators, etc. // Don't recurse into the body, but visit the decorators, etc.
for expr in decorator_list { for decorator in decorator_list {
visitor::walk_expr(self, expr); visitor::walk_decorator(self, decorator);
} }
if let Some(returns) = returns { if let Some(returns) = returns {
visitor::walk_expr(self, returns); visitor::walk_expr(self, returns);

View file

@ -63,7 +63,9 @@ fn runtime_evaluated_decorators(semantic_model: &SemanticModel, decorators: &[St
if let ScopeKind::Class(ast::StmtClassDef { decorator_list, .. }) = &semantic_model.scope().kind if let ScopeKind::Class(ast::StmtClassDef { decorator_list, .. }) = &semantic_model.scope().kind
{ {
for decorator in decorator_list.iter() { for decorator in decorator_list.iter() {
if let Some(call_path) = semantic_model.resolve_call_path(map_callable(decorator)) { if let Some(call_path) =
semantic_model.resolve_call_path(map_callable(&decorator.expression))
{
if decorators if decorators
.iter() .iter()
.any(|decorator| from_qualified_name(decorator) == call_path) .any(|decorator| from_qualified_name(decorator) == call_path)

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::{Arguments, Expr, Ranged}; use rustpython_parser::ast::{Arguments, Decorator, Ranged};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -59,7 +59,7 @@ pub(crate) fn invalid_first_argument_name_for_class_method(
checker: &Checker, checker: &Checker,
scope: &Scope, scope: &Scope,
name: &str, name: &str,
decorator_list: &[Expr], decorator_list: &[Decorator],
args: &Arguments, args: &Arguments,
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
if !matches!( if !matches!(

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::{Arguments, Expr, Ranged}; use rustpython_parser::ast::{Arguments, Decorator, Ranged};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -56,7 +56,7 @@ pub(crate) fn invalid_first_argument_name_for_method(
checker: &Checker, checker: &Checker,
scope: &Scope, scope: &Scope,
name: &str, name: &str,
decorator_list: &[Expr], decorator_list: &[Decorator],
args: &Arguments, args: &Arguments,
) -> Option<Diagnostic> { ) -> Option<Diagnostic> {
if !matches!( if !matches!(

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::{Expr, Stmt}; use rustpython_parser::ast::{Decorator, Stmt};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -51,7 +51,7 @@ impl Violation for InvalidFunctionName {
pub(crate) fn invalid_function_name( pub(crate) fn invalid_function_name(
stmt: &Stmt, stmt: &Stmt,
name: &str, name: &str,
decorator_list: &[Expr], decorator_list: &[Decorator],
ignore_names: &[String], ignore_names: &[String],
model: &SemanticModel, model: &SemanticModel,
locator: &Locator, locator: &Locator,

View file

@ -51,7 +51,7 @@ pub(crate) fn should_ignore_definition(
}) = definition }) = definition
{ {
for decorator in cast::decorator_list(stmt) { for decorator in cast::decorator_list(stmt) {
if let Some(call_path) = model.resolve_call_path(map_callable(decorator)) { if let Some(call_path) = model.resolve_call_path(map_callable(&decorator.expression)) {
if ignore_decorators if ignore_decorators
.iter() .iter()
.any(|decorator| from_qualified_name(decorator) == call_path) .any(|decorator| from_qualified_name(decorator) == call_path)

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Arguments, Expr, Stmt}; use rustpython_parser::ast::{self, Arguments, Decorator, Expr, Stmt};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -50,12 +50,12 @@ impl Violation for PropertyWithParameters {
pub(crate) fn property_with_parameters( pub(crate) fn property_with_parameters(
checker: &mut Checker, checker: &mut Checker,
stmt: &Stmt, stmt: &Stmt,
decorator_list: &[Expr], decorator_list: &[Decorator],
args: &Arguments, args: &Arguments,
) { ) {
if !decorator_list if !decorator_list
.iter() .iter()
.any(|d| matches!(&d, Expr::Name(ast::ExprName { id, .. }) if id == "property")) .any(|d| matches!(&d.expression, Expr::Name(ast::ExprName { id, .. }) if id == "property"))
{ {
return; return;
} }

View file

@ -1,6 +1,6 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use rustpython_parser::ast::{Arguments, Expr, Stmt}; use rustpython_parser::ast::{Arguments, Decorator, Stmt};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -141,7 +141,7 @@ pub(crate) fn unexpected_special_method_signature(
checker: &mut Checker, checker: &mut Checker,
stmt: &Stmt, stmt: &Stmt,
name: &str, name: &str,
decorator_list: &[Expr], decorator_list: &[Decorator],
args: &Arguments, args: &Arguments,
locator: &Locator, locator: &Locator,
) { ) {

View file

@ -1,5 +1,5 @@
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged}; use rustpython_parser::ast::{self, Constant, Decorator, Expr, Keyword, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -23,14 +23,14 @@ impl AlwaysAutofixableViolation for LRUCacheWithMaxsizeNone {
} }
/// UP033 /// UP033
pub(crate) fn lru_cache_with_maxsize_none(checker: &mut Checker, decorator_list: &[Expr]) { pub(crate) fn lru_cache_with_maxsize_none(checker: &mut Checker, decorator_list: &[Decorator]) {
for expr in decorator_list.iter() { for decorator in decorator_list.iter() {
let Expr::Call(ast::ExprCall { let Expr::Call(ast::ExprCall {
func, func,
args, args,
keywords, keywords,
range: _, range: _,
}) = expr else { }) = &decorator.expression else {
continue; continue;
}; };
@ -61,16 +61,17 @@ pub(crate) fn lru_cache_with_maxsize_none(checker: &mut Checker, decorator_list:
{ {
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
LRUCacheWithMaxsizeNone, LRUCacheWithMaxsizeNone,
TextRange::new(func.end(), expr.end()), TextRange::new(func.end(), decorator.end()),
); );
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| { diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer.get_or_import_symbol( let (import_edit, binding) = checker.importer.get_or_import_symbol(
&ImportRequest::import("functools", "cache"), &ImportRequest::import("functools", "cache"),
expr.start(), decorator.start(),
checker.semantic_model(), checker.semantic_model(),
)?; )?;
let reference_edit = Edit::range_replacement(binding, expr.range()); let reference_edit =
Edit::range_replacement(binding, decorator.expression.range());
#[allow(deprecated)] #[allow(deprecated)]
Ok(Fix::unspecified_edits(import_edit, [reference_edit])) Ok(Fix::unspecified_edits(import_edit, [reference_edit]))
}); });

View file

@ -1,5 +1,5 @@
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use rustpython_parser::ast::{self, Expr, Ranged}; use rustpython_parser::ast::{self, Decorator, Expr, Ranged};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -22,14 +22,14 @@ impl AlwaysAutofixableViolation for LRUCacheWithoutParameters {
} }
/// UP011 /// UP011
pub(crate) fn lru_cache_without_parameters(checker: &mut Checker, decorator_list: &[Expr]) { pub(crate) fn lru_cache_without_parameters(checker: &mut Checker, decorator_list: &[Decorator]) {
for expr in decorator_list.iter() { for decorator in decorator_list.iter() {
let Expr::Call(ast::ExprCall { let Expr::Call(ast::ExprCall {
func, func,
args, args,
keywords, keywords,
range: _, range: _,
}) = expr else { }) = &decorator.expression else {
continue; continue;
}; };
@ -45,13 +45,13 @@ pub(crate) fn lru_cache_without_parameters(checker: &mut Checker, decorator_list
{ {
let mut diagnostic = Diagnostic::new( let mut diagnostic = Diagnostic::new(
LRUCacheWithoutParameters, LRUCacheWithoutParameters,
TextRange::new(func.end(), expr.end()), TextRange::new(func.end(), decorator.end()),
); );
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
#[allow(deprecated)] #[allow(deprecated)]
diagnostic.set_fix(Fix::unspecified(Edit::range_replacement( diagnostic.set_fix(Fix::unspecified(Edit::range_replacement(
checker.generator().expr(func), checker.generator().expr(func),
expr.range(), decorator.expression.range(),
))); )));
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Expr, Ranged, Stmt}; use rustpython_parser::ast::{self, Decorator, Expr, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
@ -244,10 +244,10 @@ pub(crate) fn mutable_dataclass_default(checker: &mut Checker, body: &[Stmt]) {
} }
} }
pub(crate) fn is_dataclass(model: &SemanticModel, decorator_list: &[Expr]) -> bool { pub(crate) fn is_dataclass(model: &SemanticModel, decorator_list: &[Decorator]) -> bool {
decorator_list.iter().any(|decorator| { decorator_list.iter().any(|decorator| {
model model
.resolve_call_path(map_callable(decorator)) .resolve_call_path(map_callable(&decorator.expression))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["dataclasses", "dataclass"] call_path.as_slice() == ["dataclasses", "dataclass"]
}) })

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::{self, Expr, Stmt}; use rustpython_parser::ast::{self, Decorator, Stmt};
pub fn name(stmt: &Stmt) -> &str { pub fn name(stmt: &Stmt) -> &str {
match stmt { match stmt {
@ -8,7 +8,7 @@ pub fn name(stmt: &Stmt) -> &str {
} }
} }
pub fn decorator_list(stmt: &Stmt) -> &[Expr] { pub fn decorator_list(stmt: &Stmt) -> &[Decorator] {
match stmt { match stmt {
Stmt::FunctionDef(ast::StmtFunctionDef { decorator_list, .. }) Stmt::FunctionDef(ast::StmtFunctionDef { decorator_list, .. })
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { decorator_list, .. }) => { | Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { decorator_list, .. }) => {

View file

@ -2,6 +2,7 @@
//! ability to compare expressions for equality (via [`Eq`] and [`Hash`]). //! ability to compare expressions for equality (via [`Eq`] and [`Hash`]).
use num_bigint::BigInt; use num_bigint::BigInt;
use rustpython_ast::Decorator;
use rustpython_parser::ast::{ use rustpython_parser::ast::{
self, Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, ConversionFlag, self, Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, ConversionFlag,
Excepthandler, Expr, ExprContext, Identifier, Int, Keyword, MatchCase, Operator, Pattern, Stmt, Excepthandler, Expr, ExprContext, Identifier, Int, Keyword, MatchCase, Operator, Pattern, Stmt,
@ -267,6 +268,19 @@ impl<'a> From<&'a MatchCase> for ComparableMatchCase<'a> {
} }
} }
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableDecorator<'a> {
pub expression: ComparableExpr<'a>,
}
impl<'a> From<&'a Decorator> for ComparableDecorator<'a> {
fn from(decorator: &'a Decorator) -> Self {
Self {
expression: (&decorator.expression).into(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableConstant<'a> { pub enum ComparableConstant<'a> {
None, None,
@ -777,7 +791,7 @@ pub enum ComparableStmt<'a> {
name: &'a str, name: &'a str,
args: ComparableArguments<'a>, args: ComparableArguments<'a>,
body: Vec<ComparableStmt<'a>>, body: Vec<ComparableStmt<'a>>,
decorator_list: Vec<ComparableExpr<'a>>, decorator_list: Vec<ComparableDecorator<'a>>,
returns: Option<ComparableExpr<'a>>, returns: Option<ComparableExpr<'a>>,
type_comment: Option<&'a str>, type_comment: Option<&'a str>,
}, },
@ -785,7 +799,7 @@ pub enum ComparableStmt<'a> {
name: &'a str, name: &'a str,
args: ComparableArguments<'a>, args: ComparableArguments<'a>,
body: Vec<ComparableStmt<'a>>, body: Vec<ComparableStmt<'a>>,
decorator_list: Vec<ComparableExpr<'a>>, decorator_list: Vec<ComparableDecorator<'a>>,
returns: Option<ComparableExpr<'a>>, returns: Option<ComparableExpr<'a>>,
type_comment: Option<&'a str>, type_comment: Option<&'a str>,
}, },
@ -794,7 +808,7 @@ pub enum ComparableStmt<'a> {
bases: Vec<ComparableExpr<'a>>, bases: Vec<ComparableExpr<'a>>,
keywords: Vec<ComparableKeyword<'a>>, keywords: Vec<ComparableKeyword<'a>>,
body: Vec<ComparableStmt<'a>>, body: Vec<ComparableStmt<'a>>,
decorator_list: Vec<ComparableExpr<'a>>, decorator_list: Vec<ComparableDecorator<'a>>,
}, },
Return { Return {
value: Option<ComparableExpr<'a>>, value: Option<ComparableExpr<'a>>,

View file

@ -1,7 +1,7 @@
use crate::node::AnyNodeRef; use crate::node::AnyNodeRef;
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use rustpython_ast::{ use rustpython_ast::{
Arguments, Expr, Identifier, Ranged, StmtAsyncFunctionDef, StmtFunctionDef, Suite, Arguments, Decorator, Expr, Identifier, Ranged, StmtAsyncFunctionDef, StmtFunctionDef, Suite,
}; };
/// Enum that represents any python function definition. /// Enum that represents any python function definition.
@ -65,7 +65,7 @@ impl<'a> AnyFunctionDefinition<'a> {
} }
/// Returns the decorators attributing the function. /// Returns the decorators attributing the function.
pub fn decorators(self) -> &'a [Expr] { pub fn decorators(self) -> &'a [Decorator] {
match self { match self {
Self::FunctionDefinition(definition) => &definition.decorator_list, Self::FunctionDefinition(definition) => &definition.decorator_list,
Self::AsyncFunctionDefinition(definition) => &definition.decorator_list, Self::AsyncFunctionDefinition(definition) => &definition.decorator_list,

View file

@ -363,7 +363,9 @@ where
.map_or(false, |expr| any_over_expr(expr, func)) .map_or(false, |expr| any_over_expr(expr, func))
}) })
|| body.iter().any(|stmt| any_over_stmt(stmt, func)) || body.iter().any(|stmt| any_over_stmt(stmt, func))
|| decorator_list.iter().any(|expr| any_over_expr(expr, func)) || decorator_list
.iter()
.any(|decorator| any_over_expr(&decorator.expression, func))
|| returns || returns
.as_ref() .as_ref()
.map_or(false, |value| any_over_expr(value, func)) .map_or(false, |value| any_over_expr(value, func))
@ -380,7 +382,9 @@ where
.iter() .iter()
.any(|keyword| any_over_expr(&keyword.value, func)) .any(|keyword| any_over_expr(&keyword.value, func))
|| body.iter().any(|stmt| any_over_stmt(stmt, func)) || body.iter().any(|stmt| any_over_stmt(stmt, func))
|| decorator_list.iter().any(|expr| any_over_expr(expr, func)) || decorator_list
.iter()
.any(|decorator| any_over_expr(&decorator.expression, func))
} }
Stmt::Return(ast::StmtReturn { Stmt::Return(ast::StmtReturn {
value, value,

View file

@ -92,6 +92,7 @@ pub enum AnyNode {
Alias(Alias<TextRange>), Alias(Alias<TextRange>),
Withitem(Withitem<TextRange>), Withitem(Withitem<TextRange>),
MatchCase(MatchCase<TextRange>), MatchCase(MatchCase<TextRange>),
Decorator(Decorator<TextRange>),
} }
impl AnyNode { impl AnyNode {
@ -172,7 +173,8 @@ impl AnyNode {
| AnyNode::Keyword(_) | AnyNode::Keyword(_)
| AnyNode::Alias(_) | AnyNode::Alias(_)
| AnyNode::Withitem(_) | AnyNode::Withitem(_)
| AnyNode::MatchCase(_) => None, | AnyNode::MatchCase(_)
| AnyNode::Decorator(_) => None,
} }
} }
@ -253,7 +255,8 @@ impl AnyNode {
| AnyNode::Keyword(_) | AnyNode::Keyword(_)
| AnyNode::Alias(_) | AnyNode::Alias(_)
| AnyNode::Withitem(_) | AnyNode::Withitem(_)
| AnyNode::MatchCase(_) => None, | AnyNode::MatchCase(_)
| AnyNode::Decorator(_) => None,
} }
} }
@ -334,7 +337,8 @@ impl AnyNode {
| AnyNode::Keyword(_) | AnyNode::Keyword(_)
| AnyNode::Alias(_) | AnyNode::Alias(_)
| AnyNode::Withitem(_) | AnyNode::Withitem(_)
| AnyNode::MatchCase(_) => None, | AnyNode::MatchCase(_)
| AnyNode::Decorator(_) => None,
} }
} }
@ -415,7 +419,8 @@ impl AnyNode {
| AnyNode::Keyword(_) | AnyNode::Keyword(_)
| AnyNode::Alias(_) | AnyNode::Alias(_)
| AnyNode::Withitem(_) | AnyNode::Withitem(_)
| AnyNode::MatchCase(_) => None, | AnyNode::MatchCase(_)
| AnyNode::Decorator(_) => None,
} }
} }
@ -496,7 +501,8 @@ impl AnyNode {
| AnyNode::Keyword(_) | AnyNode::Keyword(_)
| AnyNode::Alias(_) | AnyNode::Alias(_)
| AnyNode::Withitem(_) | AnyNode::Withitem(_)
| AnyNode::MatchCase(_) => None, | AnyNode::MatchCase(_)
| AnyNode::Decorator(_) => None,
} }
} }
@ -577,7 +583,8 @@ impl AnyNode {
| AnyNode::Keyword(_) | AnyNode::Keyword(_)
| AnyNode::Alias(_) | AnyNode::Alias(_)
| AnyNode::Withitem(_) | AnyNode::Withitem(_)
| AnyNode::MatchCase(_) => None, | AnyNode::MatchCase(_)
| AnyNode::Decorator(_) => None,
} }
} }
@ -682,6 +689,7 @@ impl AnyNode {
Self::Alias(node) => AnyNodeRef::Alias(node), Self::Alias(node) => AnyNodeRef::Alias(node),
Self::Withitem(node) => AnyNodeRef::Withitem(node), Self::Withitem(node) => AnyNodeRef::Withitem(node),
Self::MatchCase(node) => AnyNodeRef::MatchCase(node), Self::MatchCase(node) => AnyNodeRef::MatchCase(node),
Self::Decorator(node) => AnyNodeRef::Decorator(node),
} }
} }
@ -2793,6 +2801,35 @@ impl AstNode for MatchCase<TextRange> {
} }
} }
impl AstNode for Decorator<TextRange> {
fn cast(kind: AnyNode) -> Option<Self>
where
Self: Sized,
{
if let AnyNode::Decorator(node) = kind {
Some(node)
} else {
None
}
}
fn cast_ref(kind: AnyNodeRef) -> Option<&Self> {
if let AnyNodeRef::Decorator(node) = kind {
Some(node)
} else {
None
}
}
fn as_any_node_ref(&self) -> AnyNodeRef {
AnyNodeRef::from(self)
}
fn into_any_node(self) -> AnyNode {
AnyNode::from(self)
}
}
impl From<Stmt> for AnyNode { impl From<Stmt> for AnyNode {
fn from(stmt: Stmt) -> Self { fn from(stmt: Stmt) -> Self {
match stmt { match stmt {
@ -3346,6 +3383,11 @@ impl From<MatchCase> for AnyNode {
AnyNode::MatchCase(node) AnyNode::MatchCase(node)
} }
} }
impl From<Decorator> for AnyNode {
fn from(node: Decorator) -> Self {
AnyNode::Decorator(node)
}
}
impl Ranged for AnyNode { impl Ranged for AnyNode {
fn range(&self) -> TextRange { fn range(&self) -> TextRange {
@ -3425,6 +3467,7 @@ impl Ranged for AnyNode {
AnyNode::Alias(node) => node.range(), AnyNode::Alias(node) => node.range(),
AnyNode::Withitem(node) => node.range(), AnyNode::Withitem(node) => node.range(),
AnyNode::MatchCase(node) => node.range(), AnyNode::MatchCase(node) => node.range(),
AnyNode::Decorator(node) => node.range(),
} }
} }
} }
@ -3506,6 +3549,7 @@ pub enum AnyNodeRef<'a> {
Alias(&'a Alias<TextRange>), Alias(&'a Alias<TextRange>),
Withitem(&'a Withitem<TextRange>), Withitem(&'a Withitem<TextRange>),
MatchCase(&'a MatchCase<TextRange>), MatchCase(&'a MatchCase<TextRange>),
Decorator(&'a Decorator<TextRange>),
} }
impl AnyNodeRef<'_> { impl AnyNodeRef<'_> {
@ -3586,6 +3630,7 @@ impl AnyNodeRef<'_> {
AnyNodeRef::Alias(node) => NonNull::from(*node).cast(), AnyNodeRef::Alias(node) => NonNull::from(*node).cast(),
AnyNodeRef::Withitem(node) => NonNull::from(*node).cast(), AnyNodeRef::Withitem(node) => NonNull::from(*node).cast(),
AnyNodeRef::MatchCase(node) => NonNull::from(*node).cast(), AnyNodeRef::MatchCase(node) => NonNull::from(*node).cast(),
AnyNodeRef::Decorator(node) => NonNull::from(*node).cast(),
} }
} }
@ -3672,6 +3717,7 @@ impl AnyNodeRef<'_> {
AnyNodeRef::Alias(_) => NodeKind::Alias, AnyNodeRef::Alias(_) => NodeKind::Alias,
AnyNodeRef::Withitem(_) => NodeKind::Withitem, AnyNodeRef::Withitem(_) => NodeKind::Withitem,
AnyNodeRef::MatchCase(_) => NodeKind::MatchCase, AnyNodeRef::MatchCase(_) => NodeKind::MatchCase,
AnyNodeRef::Decorator(_) => NodeKind::Decorator,
} }
} }
@ -3752,7 +3798,8 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::Keyword(_) | AnyNodeRef::Keyword(_)
| AnyNodeRef::Alias(_) | AnyNodeRef::Alias(_)
| AnyNodeRef::Withitem(_) | AnyNodeRef::Withitem(_)
| AnyNodeRef::MatchCase(_) => false, | AnyNodeRef::MatchCase(_)
| AnyNodeRef::Decorator(_) => false,
} }
} }
@ -3833,7 +3880,8 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::Keyword(_) | AnyNodeRef::Keyword(_)
| AnyNodeRef::Alias(_) | AnyNodeRef::Alias(_)
| AnyNodeRef::Withitem(_) | AnyNodeRef::Withitem(_)
| AnyNodeRef::MatchCase(_) => false, | AnyNodeRef::MatchCase(_)
| AnyNodeRef::Decorator(_) => false,
} }
} }
@ -3914,7 +3962,8 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::Keyword(_) | AnyNodeRef::Keyword(_)
| AnyNodeRef::Alias(_) | AnyNodeRef::Alias(_)
| AnyNodeRef::Withitem(_) | AnyNodeRef::Withitem(_)
| AnyNodeRef::MatchCase(_) => false, | AnyNodeRef::MatchCase(_)
| AnyNodeRef::Decorator(_) => false,
} }
} }
@ -3995,7 +4044,8 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::Keyword(_) | AnyNodeRef::Keyword(_)
| AnyNodeRef::Alias(_) | AnyNodeRef::Alias(_)
| AnyNodeRef::Withitem(_) | AnyNodeRef::Withitem(_)
| AnyNodeRef::MatchCase(_) => false, | AnyNodeRef::MatchCase(_)
| AnyNodeRef::Decorator(_) => false,
} }
} }
@ -4076,7 +4126,8 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::Keyword(_) | AnyNodeRef::Keyword(_)
| AnyNodeRef::Alias(_) | AnyNodeRef::Alias(_)
| AnyNodeRef::Withitem(_) | AnyNodeRef::Withitem(_)
| AnyNodeRef::MatchCase(_) => false, | AnyNodeRef::MatchCase(_)
| AnyNodeRef::Decorator(_) => false,
} }
} }
@ -4157,7 +4208,8 @@ impl AnyNodeRef<'_> {
| AnyNodeRef::Keyword(_) | AnyNodeRef::Keyword(_)
| AnyNodeRef::Alias(_) | AnyNodeRef::Alias(_)
| AnyNodeRef::Withitem(_) | AnyNodeRef::Withitem(_)
| AnyNodeRef::MatchCase(_) => false, | AnyNodeRef::MatchCase(_)
| AnyNodeRef::Decorator(_) => false,
} }
} }
} }
@ -4570,6 +4622,12 @@ impl<'a> From<&'a TypeIgnoreTypeIgnore> for AnyNodeRef<'a> {
} }
} }
impl<'a> From<&'a Decorator> for AnyNodeRef<'a> {
fn from(node: &'a Decorator) -> Self {
AnyNodeRef::Decorator(node)
}
}
impl<'a> From<&'a Stmt> for AnyNodeRef<'a> { impl<'a> From<&'a Stmt> for AnyNodeRef<'a> {
fn from(stmt: &'a Stmt) -> Self { fn from(stmt: &'a Stmt) -> Self {
match stmt { match stmt {
@ -4796,6 +4854,7 @@ impl Ranged for AnyNodeRef<'_> {
AnyNodeRef::Alias(node) => node.range(), AnyNodeRef::Alias(node) => node.range(),
AnyNodeRef::Withitem(node) => node.range(), AnyNodeRef::Withitem(node) => node.range(),
AnyNodeRef::MatchCase(node) => node.range(), AnyNodeRef::MatchCase(node) => node.range(),
AnyNodeRef::Decorator(node) => node.range(),
} }
} }
} }
@ -4877,4 +4936,5 @@ pub enum NodeKind {
Alias, Alias,
Withitem, Withitem,
MatchCase, MatchCase,
Decorator,
} }

View file

@ -212,7 +212,7 @@ impl<'a> Generator<'a> {
for decorator in decorator_list { for decorator in decorator_list {
statement!({ statement!({
self.p("@"); self.p("@");
self.unparse_expr(decorator, precedence::MAX); self.unparse_expr(&decorator.expression, precedence::MAX);
}); });
} }
statement!({ statement!({
@ -244,7 +244,7 @@ impl<'a> Generator<'a> {
for decorator in decorator_list { for decorator in decorator_list {
statement!({ statement!({
self.p("@"); self.p("@");
self.unparse_expr(decorator, precedence::MAX); self.unparse_expr(&decorator.expression, precedence::MAX);
}); });
} }
statement!({ statement!({
@ -276,7 +276,7 @@ impl<'a> Generator<'a> {
for decorator in decorator_list { for decorator in decorator_list {
statement!({ statement!({
self.p("@"); self.p("@");
self.unparse_expr(decorator, precedence::MAX); self.unparse_expr(&decorator.expression, precedence::MAX);
}); });
} }
statement!({ statement!({

View file

@ -2,6 +2,7 @@
pub mod preorder; pub mod preorder;
use rustpython_ast::Decorator;
use rustpython_parser::ast::{ use rustpython_parser::ast::{
self, Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Excepthandler, Expr, self, Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Excepthandler, Expr,
ExprContext, Keyword, MatchCase, Operator, Pattern, Stmt, Unaryop, Withitem, ExprContext, Keyword, MatchCase, Operator, Pattern, Stmt, Unaryop, Withitem,
@ -21,6 +22,9 @@ pub trait Visitor<'a> {
fn visit_annotation(&mut self, expr: &'a Expr) { fn visit_annotation(&mut self, expr: &'a Expr) {
walk_expr(self, expr); walk_expr(self, expr);
} }
fn visit_decorator(&mut self, decorator: &'a Decorator) {
walk_decorator(self, decorator);
}
fn visit_expr(&mut self, expr: &'a Expr) { fn visit_expr(&mut self, expr: &'a Expr) {
walk_expr(self, expr); walk_expr(self, expr);
} }
@ -93,8 +97,8 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
.. ..
}) => { }) => {
visitor.visit_arguments(args); visitor.visit_arguments(args);
for expr in decorator_list { for decorator in decorator_list {
visitor.visit_expr(expr); visitor.visit_decorator(decorator);
} }
for expr in returns { for expr in returns {
visitor.visit_annotation(expr); visitor.visit_annotation(expr);
@ -109,8 +113,8 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
.. ..
}) => { }) => {
visitor.visit_arguments(args); visitor.visit_arguments(args);
for expr in decorator_list { for decorator in decorator_list {
visitor.visit_expr(expr); visitor.visit_decorator(decorator);
} }
for expr in returns { for expr in returns {
visitor.visit_annotation(expr); visitor.visit_annotation(expr);
@ -130,8 +134,8 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
for keyword in keywords { for keyword in keywords {
visitor.visit_keyword(keyword); visitor.visit_keyword(keyword);
} }
for expr in decorator_list { for decorator in decorator_list {
visitor.visit_expr(expr); visitor.visit_decorator(decorator);
} }
visitor.visit_body(body); visitor.visit_body(body);
} }
@ -318,6 +322,10 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
} }
} }
pub fn walk_decorator<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, decorator: &'a Decorator) {
visitor.visit_expr(&decorator.expression);
}
pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) { pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
match expr { match expr {
Expr::BoolOp(ast::ExprBoolOp { Expr::BoolOp(ast::ExprBoolOp {

View file

@ -18,6 +18,10 @@ pub trait PreorderVisitor<'a> {
walk_expr(self, expr); walk_expr(self, expr);
} }
fn visit_decorator(&mut self, decorator: &'a Decorator) {
walk_decorator(self, decorator);
}
fn visit_constant(&mut self, constant: &'a Constant) { fn visit_constant(&mut self, constant: &'a Constant) {
walk_constant(self, constant); walk_constant(self, constant);
} }
@ -151,8 +155,8 @@ where
returns, returns,
.. ..
}) => { }) => {
for expr in decorator_list { for decorator in decorator_list {
visitor.visit_expr(expr); visitor.visit_decorator(decorator);
} }
visitor.visit_arguments(args); visitor.visit_arguments(args);
@ -171,8 +175,8 @@ where
decorator_list, decorator_list,
.. ..
}) => { }) => {
for expr in decorator_list { for decorator in decorator_list {
visitor.visit_expr(expr); visitor.visit_decorator(decorator);
} }
for expr in bases { for expr in bases {
@ -387,6 +391,13 @@ where
} }
} }
pub fn walk_decorator<'a, V>(visitor: &mut V, decorator: &'a Decorator)
where
V: PreorderVisitor<'a> + ?Sized,
{
visitor.visit_expr(&decorator.expression);
}
pub fn walk_expr<'a, V>(visitor: &mut V, expr: &'a Expr) pub fn walk_expr<'a, V>(visitor: &mut V, expr: &'a Expr)
where where
V: PreorderVisitor<'a> + ?Sized, V: PreorderVisitor<'a> + ?Sized,

View file

@ -745,9 +745,13 @@ fn find_pos_only_slash_offset(
), ),
locator.contents(), locator.contents(),
) )
.map(|(offset, c)| { .and_then(|(offset, c)| {
debug_assert_eq!(c, '/'); if c == '/' {
offset Some(offset)
} else {
debug_assert_eq!(c, ')');
None
}
}) })
}, },
) )

View file

@ -5,8 +5,8 @@ expression: comments.debug(test_case.source_code)
{ {
Node { Node {
kind: Arguments, kind: Arguments,
range: 10..94, range: 9..96,
source: `a=10,/, # trailing position...t comment.⏎`, source: `(a=10,/, # trailing positio...t comment.⏎`,
}: { }: {
"leading": [], "leading": [],
"dangling": [ "dangling": [

View file

@ -5,8 +5,8 @@ expression: comments.debug(test_case.source_code)
{ {
Node { Node {
kind: Arguments, kind: Arguments,
range: 15..177, range: 9..179,
source: `a=10 # trailing positional comment⏎`, source: `(⏎`,
}: { }: {
"leading": [], "leading": [],
"dangling": [ "dangling": [

View file

@ -3,25 +3,10 @@ source: crates/ruff_python_formatter/src/comments/mod.rs
expression: comments.debug(test_case.source_code) expression: comments.debug(test_case.source_code)
--- ---
{ {
Node {
kind: Arg,
range: 15..16,
source: `a`,
}: {
"leading": [],
"dangling": [],
"trailing": [
SourceComment {
text: "# trailing positional comment",
position: EndOfLine,
formatted: false,
},
],
},
Node { Node {
kind: Arguments, kind: Arguments,
range: 15..168, range: 9..170,
source: `a, # trailing positional comment⏎`, source: `(⏎`,
}: { }: {
"leading": [], "leading": [],
"dangling": [ "dangling": [
@ -38,6 +23,21 @@ expression: comments.debug(test_case.source_code)
], ],
"trailing": [], "trailing": [],
}, },
Node {
kind: Arg,
range: 15..16,
source: `a`,
}: {
"leading": [],
"dangling": [],
"trailing": [
SourceComment {
text: "# trailing positional comment",
position: EndOfLine,
formatted: false,
},
],
},
Node { Node {
kind: Arg, kind: Arg,
range: 166..167, range: 166..167,

View file

@ -3,6 +3,26 @@ source: crates/ruff_python_formatter/src/comments/mod.rs
expression: comments.debug(test_case.source_code) expression: comments.debug(test_case.source_code)
--- ---
{ {
Node {
kind: Arguments,
range: 9..166,
source: `(⏎`,
}: {
"leading": [],
"dangling": [
SourceComment {
text: "# Positional arguments only after here",
position: OwnLine,
formatted: false,
},
SourceComment {
text: "# trailing positional argument comment.",
position: EndOfLine,
formatted: false,
},
],
"trailing": [],
},
Node { Node {
kind: Arg, kind: Arg,
range: 15..16, range: 15..16,
@ -16,42 +36,11 @@ expression: comments.debug(test_case.source_code)
position: EndOfLine, position: EndOfLine,
formatted: false, formatted: false,
}, },
],
},
Node {
kind: Arguments,
range: 15..97,
source: `a, # trailing positional comment⏎`,
}: {
"leading": [],
"dangling": [
SourceComment {
text: "# Positional arguments only after here",
position: OwnLine,
formatted: false,
},
],
"trailing": [
SourceComment {
text: "# trailing positional argument comment.",
position: EndOfLine,
formatted: false,
},
],
},
Node {
kind: StmtPass,
range: 168..172,
source: `pass`,
}: {
"leading": [
SourceComment { SourceComment {
text: "# Trailing on new line", text: "# Trailing on new line",
position: OwnLine, position: OwnLine,
formatted: false, formatted: false,
}, },
], ],
"dangling": [],
"trailing": [],
}, },
} }

View file

@ -3,25 +3,10 @@ source: crates/ruff_python_formatter/src/comments/mod.rs
expression: comments.debug(test_case.source_code) expression: comments.debug(test_case.source_code)
--- ---
{ {
Node {
kind: Arg,
range: 15..16,
source: `a`,
}: {
"leading": [],
"dangling": [],
"trailing": [
SourceComment {
text: "# trailing positional comment",
position: EndOfLine,
formatted: false,
},
],
},
Node { Node {
kind: Arguments, kind: Arguments,
range: 15..168, range: 9..170,
source: `a # trailing positional comment⏎`, source: `(⏎`,
}: { }: {
"leading": [], "leading": [],
"dangling": [ "dangling": [
@ -38,6 +23,21 @@ expression: comments.debug(test_case.source_code)
], ],
"trailing": [], "trailing": [],
}, },
Node {
kind: Arg,
range: 15..16,
source: `a`,
}: {
"leading": [],
"dangling": [],
"trailing": [
SourceComment {
text: "# trailing positional comment",
position: EndOfLine,
formatted: false,
},
],
},
Node { Node {
kind: Arg, kind: Arg,
range: 166..167, range: 166..167,

View file

@ -1,4 +1,4 @@
use rustpython_parser::ast::Expr; use rustpython_parser::ast::Decorator;
use ruff_python_ast::call_path::from_qualified_name; use ruff_python_ast::call_path::from_qualified_name;
use ruff_python_ast::helpers::map_callable; use ruff_python_ast::helpers::map_callable;
@ -22,18 +22,18 @@ pub fn classify(
model: &SemanticModel, model: &SemanticModel,
scope: &Scope, scope: &Scope,
name: &str, name: &str,
decorator_list: &[Expr], decorator_list: &[Decorator],
classmethod_decorators: &[String], classmethod_decorators: &[String],
staticmethod_decorators: &[String], staticmethod_decorators: &[String],
) -> FunctionType { ) -> FunctionType {
let ScopeKind::Class(scope) = &scope.kind else { let ScopeKind::Class(scope) = &scope.kind else {
return FunctionType::Function; return FunctionType::Function;
}; };
if decorator_list.iter().any(|expr| { if decorator_list.iter().any(|decorator| {
// The method is decorated with a static method decorator (like // The method is decorated with a static method decorator (like
// `@staticmethod`). // `@staticmethod`).
model model
.resolve_call_path(map_callable(expr)) .resolve_call_path(map_callable(&decorator.expression))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["", "staticmethod"] call_path.as_slice() == ["", "staticmethod"]
|| staticmethod_decorators || staticmethod_decorators
@ -52,9 +52,9 @@ pub fn classify(
.any(|(module, member)| call_path.as_slice() == [*module, *member]) .any(|(module, member)| call_path.as_slice() == [*module, *member])
}) })
}) })
|| decorator_list.iter().any(|expr| { || decorator_list.iter().any(|decorator| {
// The method is decorated with a class method decorator (like `@classmethod`). // The method is decorated with a class method decorator (like `@classmethod`).
model.resolve_call_path(map_callable(expr)).map_or(false, |call_path| { model.resolve_call_path(map_callable(&decorator.expression)).map_or(false, |call_path| {
call_path.as_slice() == ["", "classmethod"] || call_path.as_slice() == ["", "classmethod"] ||
classmethod_decorators classmethod_decorators
.iter() .iter()

View file

@ -1,6 +1,6 @@
use std::path::Path; use std::path::Path;
use rustpython_parser::ast::{self, Expr, Stmt}; use rustpython_parser::ast::{self, Decorator, Stmt};
use ruff_python_ast::call_path::{collect_call_path, CallPath}; use ruff_python_ast::call_path::{collect_call_path, CallPath};
use ruff_python_ast::helpers::map_callable; use ruff_python_ast::helpers::map_callable;
@ -14,10 +14,10 @@ pub enum Visibility {
} }
/// Returns `true` if a function is a "static method". /// Returns `true` if a function is a "static method".
pub fn is_staticmethod(model: &SemanticModel, decorator_list: &[Expr]) -> bool { pub fn is_staticmethod(model: &SemanticModel, decorator_list: &[Decorator]) -> bool {
decorator_list.iter().any(|expr| { decorator_list.iter().any(|decorator| {
model model
.resolve_call_path(map_callable(expr)) .resolve_call_path(map_callable(&decorator.expression))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["", "staticmethod"] call_path.as_slice() == ["", "staticmethod"]
}) })
@ -25,10 +25,10 @@ pub fn is_staticmethod(model: &SemanticModel, decorator_list: &[Expr]) -> bool {
} }
/// Returns `true` if a function is a "class method". /// Returns `true` if a function is a "class method".
pub fn is_classmethod(model: &SemanticModel, decorator_list: &[Expr]) -> bool { pub fn is_classmethod(model: &SemanticModel, decorator_list: &[Decorator]) -> bool {
decorator_list.iter().any(|expr| { decorator_list.iter().any(|decorator| {
model model
.resolve_call_path(map_callable(expr)) .resolve_call_path(map_callable(&decorator.expression))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["", "classmethod"] call_path.as_slice() == ["", "classmethod"]
}) })
@ -36,24 +36,24 @@ pub fn is_classmethod(model: &SemanticModel, decorator_list: &[Expr]) -> bool {
} }
/// Returns `true` if a function definition is an `@overload`. /// Returns `true` if a function definition is an `@overload`.
pub fn is_overload(model: &SemanticModel, decorator_list: &[Expr]) -> bool { pub fn is_overload(model: &SemanticModel, decorator_list: &[Decorator]) -> bool {
decorator_list decorator_list
.iter() .iter()
.any(|expr| model.match_typing_expr(map_callable(expr), "overload")) .any(|decorator| model.match_typing_expr(map_callable(&decorator.expression), "overload"))
} }
/// Returns `true` if a function definition is an `@override` (PEP 698). /// Returns `true` if a function definition is an `@override` (PEP 698).
pub fn is_override(model: &SemanticModel, decorator_list: &[Expr]) -> bool { pub fn is_override(model: &SemanticModel, decorator_list: &[Decorator]) -> bool {
decorator_list decorator_list
.iter() .iter()
.any(|expr| model.match_typing_expr(map_callable(expr), "override")) .any(|decorator| model.match_typing_expr(map_callable(&decorator.expression), "override"))
} }
/// Returns `true` if a function definition is an abstract method based on its decorators. /// Returns `true` if a function definition is an abstract method based on its decorators.
pub fn is_abstract(model: &SemanticModel, decorator_list: &[Expr]) -> bool { pub fn is_abstract(model: &SemanticModel, decorator_list: &[Decorator]) -> bool {
decorator_list.iter().any(|expr| { decorator_list.iter().any(|decorator| {
model model
.resolve_call_path(map_callable(expr)) .resolve_call_path(map_callable(&decorator.expression))
.map_or(false, |call_path| { .map_or(false, |call_path| {
matches!( matches!(
call_path.as_slice(), call_path.as_slice(),
@ -74,12 +74,12 @@ pub fn is_abstract(model: &SemanticModel, decorator_list: &[Expr]) -> bool {
/// `@property`-like decorators. /// `@property`-like decorators.
pub fn is_property( pub fn is_property(
model: &SemanticModel, model: &SemanticModel,
decorator_list: &[Expr], decorator_list: &[Decorator],
extra_properties: &[CallPath], extra_properties: &[CallPath],
) -> bool { ) -> bool {
decorator_list.iter().any(|expr| { decorator_list.iter().any(|decorator| {
model model
.resolve_call_path(map_callable(expr)) .resolve_call_path(map_callable(&decorator.expression))
.map_or(false, |call_path| { .map_or(false, |call_path| {
call_path.as_slice() == ["", "property"] call_path.as_slice() == ["", "property"]
|| call_path.as_slice() == ["functools", "cached_property"] || call_path.as_slice() == ["functools", "cached_property"]
@ -91,10 +91,10 @@ pub fn is_property(
} }
/// Returns `true` if a class is an `final`. /// Returns `true` if a class is an `final`.
pub fn is_final(model: &SemanticModel, decorator_list: &[Expr]) -> bool { pub fn is_final(model: &SemanticModel, decorator_list: &[Decorator]) -> bool {
decorator_list decorator_list
.iter() .iter()
.any(|expr| model.match_typing_expr(map_callable(expr), "final")) .any(|decorator| model.match_typing_expr(map_callable(&decorator.expression), "final"))
} }
/// Returns `true` if a function is a "magic method". /// Returns `true` if a function is a "magic method".
@ -206,8 +206,8 @@ pub(crate) fn method_visibility(stmt: &Stmt) -> Visibility {
.. ..
}) => { }) => {
// Is this a setter or deleter? // Is this a setter or deleter?
if decorator_list.iter().any(|expr| { if decorator_list.iter().any(|decorator| {
collect_call_path(expr).map_or(false, |call_path| { collect_call_path(&decorator.expression).map_or(false, |call_path| {
call_path.as_slice() == [name, "setter"] call_path.as_slice() == [name, "setter"]
|| call_path.as_slice() == [name, "deleter"] || call_path.as_slice() == [name, "deleter"]
}) })