Skip to content

Commit 4c8d612

Browse files
Enforce pytest import for decorators (#18779)
1 parent 65b288b commit 4c8d612

File tree

6 files changed

+145
-139
lines changed

6 files changed

+145
-139
lines changed

crates/ruff_linter/resources/test/fixtures/flake8_pytest_style/PT023.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
@pytest.mark.foo(scope="module")
2+
def ok_due_to_missing_import():
3+
pass
4+
5+
16
import pytest
27

38

crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -896,7 +896,7 @@ fn check_fixture_addfinalizer(checker: &Checker, parameters: &Parameters, body:
896896

897897
/// PT024, PT025
898898
fn check_fixture_marks(checker: &Checker, decorators: &[Decorator]) {
899-
for (expr, marker) in get_mark_decorators(decorators) {
899+
for (expr, marker) in get_mark_decorators(decorators, checker.semantic()) {
900900
if checker.enabled(Rule::PytestUnnecessaryAsyncioMarkOnFixture) {
901901
if marker == "asyncio" {
902902
let mut diagnostic =

crates/ruff_linter/src/rules/flake8_pytest_style/rules/helpers.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
1-
use crate::checkers::ast::Checker;
1+
use std::fmt;
2+
23
use ruff_python_ast::helpers::map_callable;
3-
use ruff_python_ast::name::UnqualifiedName;
44
use ruff_python_ast::{self as ast, Decorator, Expr, ExprCall, Keyword, Stmt, StmtFunctionDef};
55
use ruff_python_semantic::analyze::visibility;
66
use ruff_python_semantic::{ScopeKind, SemanticModel};
77
use ruff_python_trivia::PythonWhitespace;
8-
use std::fmt;
98

10-
pub(super) fn get_mark_decorators(
11-
decorators: &[Decorator],
12-
) -> impl Iterator<Item = (&Decorator, &str)> {
13-
decorators.iter().filter_map(|decorator| {
14-
let name = UnqualifiedName::from_expr(map_callable(&decorator.expression))?;
15-
let ["pytest", "mark", marker] = name.segments() else {
16-
return None;
17-
};
18-
Some((decorator, *marker))
9+
use crate::checkers::ast::Checker;
10+
11+
pub(super) fn get_mark_decorators<'a>(
12+
decorators: &'a [Decorator],
13+
semantic: &'a SemanticModel,
14+
) -> impl Iterator<Item = (&'a Decorator, &'a str)> + 'a {
15+
decorators.iter().filter_map(move |decorator| {
16+
let expr = map_callable(&decorator.expression);
17+
let qualified_name = semantic.resolve_qualified_name(expr)?;
18+
match qualified_name.segments() {
19+
["pytest", "mark", marker] => Some((decorator, *marker)),
20+
_ => None,
21+
}
1922
})
2023
}
2124

crates/ruff_linter/src/rules/flake8_pytest_style/rules/marks.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ pub(crate) fn marks(checker: &Checker, decorators: &[Decorator]) {
214214
let enforce_parentheses = checker.enabled(Rule::PytestIncorrectMarkParenthesesStyle);
215215
let enforce_useless_usefixtures = checker.enabled(Rule::PytestUseFixturesWithoutParameters);
216216

217-
for (decorator, marker) in get_mark_decorators(decorators) {
217+
for (decorator, marker) in get_mark_decorators(decorators, checker.semantic()) {
218218
if enforce_parentheses {
219219
check_mark_parentheses(checker, decorator, marker);
220220
}
Lines changed: 61 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,100 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
3-
snapshot_kind: text
43
---
5-
PT023.py:46:1: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
6-
|
7-
46 | @pytest.mark.foo()
8-
| ^^^^^^^^^^^^^^^^^^ PT023
9-
47 | def test_something():
10-
48 | pass
11-
|
12-
= help: Remove parentheses
13-
14-
Safe fix
15-
43 43 | # With parentheses
16-
44 44 |
17-
45 45 |
18-
46 |-@pytest.mark.foo()
19-
46 |+@pytest.mark.foo
20-
47 47 | def test_something():
21-
48 48 | pass
22-
49 49 |
23-
244
PT023.py:51:1: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
255
|
266
51 | @pytest.mark.foo()
277
| ^^^^^^^^^^^^^^^^^^ PT023
28-
52 | class TestClass:
29-
53 | def test_something():
8+
52 | def test_something():
9+
53 | pass
3010
|
3111
= help: Remove parentheses
3212

3313
Safe fix
34-
48 48 | pass
14+
48 48 | # With parentheses
3515
49 49 |
3616
50 50 |
3717
51 |-@pytest.mark.foo()
3818
51 |+@pytest.mark.foo
39-
52 52 | class TestClass:
40-
53 53 | def test_something():
41-
54 54 | pass
19+
52 52 | def test_something():
20+
53 53 | pass
21+
54 54 |
4222

43-
PT023.py:58:5: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
23+
PT023.py:56:1: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
4424
|
25+
56 | @pytest.mark.foo()
26+
| ^^^^^^^^^^^^^^^^^^ PT023
4527
57 | class TestClass:
46-
58 | @pytest.mark.foo()
47-
| ^^^^^^^^^^^^^^^^^^ PT023
48-
59 | def test_something():
49-
60 | pass
28+
58 | def test_something():
5029
|
5130
= help: Remove parentheses
5231

5332
Safe fix
33+
53 53 | pass
34+
54 54 |
5435
55 55 |
55-
56 56 |
36+
56 |-@pytest.mark.foo()
37+
56 |+@pytest.mark.foo
5638
57 57 | class TestClass:
57-
58 |- @pytest.mark.foo()
58-
58 |+ @pytest.mark.foo
59-
59 59 | def test_something():
60-
60 60 | pass
61-
61 61 |
39+
58 58 | def test_something():
40+
59 59 | pass
6241

63-
PT023.py:64:5: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
42+
PT023.py:63:5: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
6443
|
65-
63 | class TestClass:
66-
64 | @pytest.mark.foo()
44+
62 | class TestClass:
45+
63 | @pytest.mark.foo()
6746
| ^^^^^^^^^^^^^^^^^^ PT023
68-
65 | class TestNestedClass:
69-
66 | def test_something():
47+
64 | def test_something():
48+
65 | pass
7049
|
7150
= help: Remove parentheses
7251

7352
Safe fix
53+
60 60 |
7454
61 61 |
75-
62 62 |
76-
63 63 | class TestClass:
77-
64 |- @pytest.mark.foo()
78-
64 |+ @pytest.mark.foo
79-
65 65 | class TestNestedClass:
80-
66 66 | def test_something():
81-
67 67 | pass
55+
62 62 | class TestClass:
56+
63 |- @pytest.mark.foo()
57+
63 |+ @pytest.mark.foo
58+
64 64 | def test_something():
59+
65 65 | pass
60+
66 66 |
61+
62+
PT023.py:69:5: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
63+
|
64+
68 | class TestClass:
65+
69 | @pytest.mark.foo()
66+
| ^^^^^^^^^^^^^^^^^^ PT023
67+
70 | class TestNestedClass:
68+
71 | def test_something():
69+
|
70+
= help: Remove parentheses
71+
72+
Safe fix
73+
66 66 |
74+
67 67 |
75+
68 68 | class TestClass:
76+
69 |- @pytest.mark.foo()
77+
69 |+ @pytest.mark.foo
78+
70 70 | class TestNestedClass:
79+
71 71 | def test_something():
80+
72 72 | pass
8281

83-
PT023.py:72:9: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
82+
PT023.py:77:9: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
8483
|
85-
70 | class TestClass:
86-
71 | class TestNestedClass:
87-
72 | @pytest.mark.foo()
84+
75 | class TestClass:
85+
76 | class TestNestedClass:
86+
77 | @pytest.mark.foo()
8887
| ^^^^^^^^^^^^^^^^^^ PT023
89-
73 | def test_something():
90-
74 | pass
88+
78 | def test_something():
89+
79 | pass
9190
|
9291
= help: Remove parentheses
9392

9493
Safe fix
95-
69 69 |
96-
70 70 | class TestClass:
97-
71 71 | class TestNestedClass:
98-
72 |- @pytest.mark.foo()
99-
72 |+ @pytest.mark.foo
100-
73 73 | def test_something():
101-
74 74 | pass
94+
74 74 |
95+
75 75 | class TestClass:
96+
76 76 | class TestNestedClass:
97+
77 |- @pytest.mark.foo()
98+
77 |+ @pytest.mark.foo
99+
78 78 | def test_something():
100+
79 79 | pass
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,101 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
3-
snapshot_kind: text
43
---
5-
PT023.py:12:1: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
6-
|
7-
12 | @pytest.mark.foo
8-
| ^^^^^^^^^^^^^^^^ PT023
9-
13 | def test_something():
10-
14 | pass
11-
|
12-
= help: Add parentheses
13-
14-
Safe fix
15-
9 9 | # Without parentheses
16-
10 10 |
17-
11 11 |
18-
12 |-@pytest.mark.foo
19-
12 |+@pytest.mark.foo()
20-
13 13 | def test_something():
21-
14 14 | pass
22-
15 15 |
23-
244
PT023.py:17:1: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
255
|
266
17 | @pytest.mark.foo
277
| ^^^^^^^^^^^^^^^^ PT023
28-
18 | class TestClass:
29-
19 | def test_something():
8+
18 | def test_something():
9+
19 | pass
3010
|
3111
= help: Add parentheses
3212

3313
Safe fix
34-
14 14 | pass
14+
14 14 | # Without parentheses
3515
15 15 |
3616
16 16 |
3717
17 |-@pytest.mark.foo
3818
17 |+@pytest.mark.foo()
39-
18 18 | class TestClass:
40-
19 19 | def test_something():
41-
20 20 | pass
19+
18 18 | def test_something():
20+
19 19 | pass
21+
20 20 |
4222

43-
PT023.py:24:5: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
23+
PT023.py:22:1: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
4424
|
25+
22 | @pytest.mark.foo
26+
| ^^^^^^^^^^^^^^^^ PT023
4527
23 | class TestClass:
46-
24 | @pytest.mark.foo
47-
| ^^^^^^^^^^^^^^^^ PT023
48-
25 | def test_something():
49-
26 | pass
28+
24 | def test_something():
5029
|
5130
= help: Add parentheses
5231

5332
Safe fix
33+
19 19 | pass
34+
20 20 |
5435
21 21 |
55-
22 22 |
36+
22 |-@pytest.mark.foo
37+
22 |+@pytest.mark.foo()
5638
23 23 | class TestClass:
57-
24 |- @pytest.mark.foo
58-
24 |+ @pytest.mark.foo()
59-
25 25 | def test_something():
60-
26 26 | pass
61-
27 27 |
39+
24 24 | def test_something():
40+
25 25 | pass
6241

63-
PT023.py:30:5: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
42+
PT023.py:29:5: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
6443
|
65-
29 | class TestClass:
66-
30 | @pytest.mark.foo
44+
28 | class TestClass:
45+
29 | @pytest.mark.foo
6746
| ^^^^^^^^^^^^^^^^ PT023
68-
31 | class TestNestedClass:
69-
32 | def test_something():
47+
30 | def test_something():
48+
31 | pass
7049
|
7150
= help: Add parentheses
7251

7352
Safe fix
53+
26 26 |
7454
27 27 |
75-
28 28 |
76-
29 29 | class TestClass:
77-
30 |- @pytest.mark.foo
78-
30 |+ @pytest.mark.foo()
79-
31 31 | class TestNestedClass:
80-
32 32 | def test_something():
81-
33 33 | pass
55+
28 28 | class TestClass:
56+
29 |- @pytest.mark.foo
57+
29 |+ @pytest.mark.foo()
58+
30 30 | def test_something():
59+
31 31 | pass
60+
32 32 |
61+
62+
PT023.py:35:5: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
63+
|
64+
34 | class TestClass:
65+
35 | @pytest.mark.foo
66+
| ^^^^^^^^^^^^^^^^ PT023
67+
36 | class TestNestedClass:
68+
37 | def test_something():
69+
|
70+
= help: Add parentheses
71+
72+
Safe fix
73+
32 32 |
74+
33 33 |
75+
34 34 | class TestClass:
76+
35 |- @pytest.mark.foo
77+
35 |+ @pytest.mark.foo()
78+
36 36 | class TestNestedClass:
79+
37 37 | def test_something():
80+
38 38 | pass
8281

83-
PT023.py:38:9: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
82+
PT023.py:43:9: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
8483
|
85-
36 | class TestClass:
86-
37 | class TestNestedClass:
87-
38 | @pytest.mark.foo
84+
41 | class TestClass:
85+
42 | class TestNestedClass:
86+
43 | @pytest.mark.foo
8887
| ^^^^^^^^^^^^^^^^ PT023
89-
39 | def test_something():
90-
40 | pass
88+
44 | def test_something():
89+
45 | pass
9190
|
9291
= help: Add parentheses
9392

9493
Safe fix
95-
35 35 |
96-
36 36 | class TestClass:
97-
37 37 | class TestNestedClass:
98-
38 |- @pytest.mark.foo
99-
38 |+ @pytest.mark.foo()
100-
39 39 | def test_something():
101-
40 40 | pass
102-
41 41 |
94+
40 40 |
95+
41 41 | class TestClass:
96+
42 42 | class TestNestedClass:
97+
43 |- @pytest.mark.foo
98+
43 |+ @pytest.mark.foo()
99+
44 44 | def test_something():
100+
45 45 | pass
101+
46 46 |

0 commit comments

Comments
 (0)