Enforce pytest import for decorators (#18779)

This commit is contained in:
Charlie Marsh 2025-06-19 05:49:34 -04:00 committed by GitHub
parent 65b288b45b
commit 4c8d612120
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 147 additions and 141 deletions

View file

@ -1,3 +1,8 @@
@pytest.mark.foo(scope="module")
def ok_due_to_missing_import():
pass
import pytest

View file

@ -896,7 +896,7 @@ fn check_fixture_addfinalizer(checker: &Checker, parameters: &Parameters, body:
/// PT024, PT025
fn check_fixture_marks(checker: &Checker, decorators: &[Decorator]) {
for (expr, marker) in get_mark_decorators(decorators) {
for (expr, marker) in get_mark_decorators(decorators, checker.semantic()) {
if checker.enabled(Rule::PytestUnnecessaryAsyncioMarkOnFixture) {
if marker == "asyncio" {
let mut diagnostic =

View file

@ -1,21 +1,24 @@
use crate::checkers::ast::Checker;
use std::fmt;
use ruff_python_ast::helpers::map_callable;
use ruff_python_ast::name::UnqualifiedName;
use ruff_python_ast::{self as ast, Decorator, Expr, ExprCall, Keyword, Stmt, StmtFunctionDef};
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::{ScopeKind, SemanticModel};
use ruff_python_trivia::PythonWhitespace;
use std::fmt;
pub(super) fn get_mark_decorators(
decorators: &[Decorator],
) -> impl Iterator<Item = (&Decorator, &str)> {
decorators.iter().filter_map(|decorator| {
let name = UnqualifiedName::from_expr(map_callable(&decorator.expression))?;
let ["pytest", "mark", marker] = name.segments() else {
return None;
};
Some((decorator, *marker))
use crate::checkers::ast::Checker;
pub(super) fn get_mark_decorators<'a>(
decorators: &'a [Decorator],
semantic: &'a SemanticModel,
) -> impl Iterator<Item = (&'a Decorator, &'a str)> + 'a {
decorators.iter().filter_map(move |decorator| {
let expr = map_callable(&decorator.expression);
let qualified_name = semantic.resolve_qualified_name(expr)?;
match qualified_name.segments() {
["pytest", "mark", marker] => Some((decorator, *marker)),
_ => None,
}
})
}

View file

@ -214,7 +214,7 @@ pub(crate) fn marks(checker: &Checker, decorators: &[Decorator]) {
let enforce_parentheses = checker.enabled(Rule::PytestIncorrectMarkParenthesesStyle);
let enforce_useless_usefixtures = checker.enabled(Rule::PytestUseFixturesWithoutParameters);
for (decorator, marker) in get_mark_decorators(decorators) {
for (decorator, marker) in get_mark_decorators(decorators, checker.semantic()) {
if enforce_parentheses {
check_mark_parentheses(checker, decorator, marker);
}

View file

@ -1,101 +1,100 @@
---
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
snapshot_kind: text
---
PT023.py:46:1: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
|
46 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^^ PT023
47 | def test_something():
48 | pass
|
= help: Remove parentheses
Safe fix
43 43 | # With parentheses
44 44 |
45 45 |
46 |-@pytest.mark.foo()
46 |+@pytest.mark.foo
47 47 | def test_something():
48 48 | pass
49 49 |
PT023.py:51:1: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
|
51 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^^ PT023
52 | class TestClass:
53 | def test_something():
52 | def test_something():
53 | pass
|
= help: Remove parentheses
Safe fix
48 48 | pass
48 48 | # With parentheses
49 49 |
50 50 |
51 |-@pytest.mark.foo()
51 |+@pytest.mark.foo
52 52 | class TestClass:
53 53 | def test_something():
54 54 | pass
52 52 | def test_something():
53 53 | pass
54 54 |
PT023.py:58:5: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
PT023.py:56:1: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
|
56 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^^ PT023
57 | class TestClass:
58 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^^ PT023
59 | def test_something():
60 | pass
58 | def test_something():
|
= help: Remove parentheses
Safe fix
53 53 | pass
54 54 |
55 55 |
56 56 |
56 |-@pytest.mark.foo()
56 |+@pytest.mark.foo
57 57 | class TestClass:
58 |- @pytest.mark.foo()
58 |+ @pytest.mark.foo
59 59 | def test_something():
60 60 | pass
61 61 |
58 58 | def test_something():
59 59 | pass
PT023.py:64:5: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
PT023.py:63:5: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
|
63 | class TestClass:
64 | @pytest.mark.foo()
62 | class TestClass:
63 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^^ PT023
65 | class TestNestedClass:
66 | def test_something():
64 | def test_something():
65 | pass
|
= help: Remove parentheses
Safe fix
60 60 |
61 61 |
62 62 |
63 63 | class TestClass:
64 |- @pytest.mark.foo()
64 |+ @pytest.mark.foo
65 65 | class TestNestedClass:
66 66 | def test_something():
67 67 | pass
62 62 | class TestClass:
63 |- @pytest.mark.foo()
63 |+ @pytest.mark.foo
64 64 | def test_something():
65 65 | pass
66 66 |
PT023.py:72:9: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
PT023.py:69:5: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
|
70 | class TestClass:
71 | class TestNestedClass:
72 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^^ PT023
73 | def test_something():
74 | pass
68 | class TestClass:
69 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^^ PT023
70 | class TestNestedClass:
71 | def test_something():
|
= help: Remove parentheses
Safe fix
69 69 |
70 70 | class TestClass:
71 71 | class TestNestedClass:
72 |- @pytest.mark.foo()
72 |+ @pytest.mark.foo
73 73 | def test_something():
74 74 | pass
66 66 |
67 67 |
68 68 | class TestClass:
69 |- @pytest.mark.foo()
69 |+ @pytest.mark.foo
70 70 | class TestNestedClass:
71 71 | def test_something():
72 72 | pass
PT023.py:77:9: PT023 [*] Use `@pytest.mark.foo` over `@pytest.mark.foo()`
|
75 | class TestClass:
76 | class TestNestedClass:
77 | @pytest.mark.foo()
| ^^^^^^^^^^^^^^^^^^ PT023
78 | def test_something():
79 | pass
|
= help: Remove parentheses
Safe fix
74 74 |
75 75 | class TestClass:
76 76 | class TestNestedClass:
77 |- @pytest.mark.foo()
77 |+ @pytest.mark.foo
78 78 | def test_something():
79 79 | pass

View file

@ -1,102 +1,101 @@
---
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
snapshot_kind: text
---
PT023.py:12:1: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
|
12 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^^ PT023
13 | def test_something():
14 | pass
|
= help: Add parentheses
Safe fix
9 9 | # Without parentheses
10 10 |
11 11 |
12 |-@pytest.mark.foo
12 |+@pytest.mark.foo()
13 13 | def test_something():
14 14 | pass
15 15 |
PT023.py:17:1: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
|
17 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^^ PT023
18 | class TestClass:
19 | def test_something():
18 | def test_something():
19 | pass
|
= help: Add parentheses
Safe fix
14 14 | pass
14 14 | # Without parentheses
15 15 |
16 16 |
17 |-@pytest.mark.foo
17 |+@pytest.mark.foo()
18 18 | class TestClass:
19 19 | def test_something():
20 20 | pass
18 18 | def test_something():
19 19 | pass
20 20 |
PT023.py:24:5: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
PT023.py:22:1: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
|
22 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^^ PT023
23 | class TestClass:
24 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^^ PT023
25 | def test_something():
26 | pass
24 | def test_something():
|
= help: Add parentheses
Safe fix
19 19 | pass
20 20 |
21 21 |
22 22 |
22 |-@pytest.mark.foo
22 |+@pytest.mark.foo()
23 23 | class TestClass:
24 |- @pytest.mark.foo
24 |+ @pytest.mark.foo()
25 25 | def test_something():
26 26 | pass
27 27 |
24 24 | def test_something():
25 25 | pass
PT023.py:30:5: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
PT023.py:29:5: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
|
29 | class TestClass:
30 | @pytest.mark.foo
28 | class TestClass:
29 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^^ PT023
31 | class TestNestedClass:
32 | def test_something():
30 | def test_something():
31 | pass
|
= help: Add parentheses
Safe fix
26 26 |
27 27 |
28 28 |
29 29 | class TestClass:
30 |- @pytest.mark.foo
30 |+ @pytest.mark.foo()
31 31 | class TestNestedClass:
32 32 | def test_something():
33 33 | pass
28 28 | class TestClass:
29 |- @pytest.mark.foo
29 |+ @pytest.mark.foo()
30 30 | def test_something():
31 31 | pass
32 32 |
PT023.py:38:9: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
PT023.py:35:5: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
|
36 | class TestClass:
37 | class TestNestedClass:
38 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^^ PT023
39 | def test_something():
40 | pass
34 | class TestClass:
35 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^^ PT023
36 | class TestNestedClass:
37 | def test_something():
|
= help: Add parentheses
Safe fix
35 35 |
36 36 | class TestClass:
37 37 | class TestNestedClass:
38 |- @pytest.mark.foo
38 |+ @pytest.mark.foo()
39 39 | def test_something():
40 40 | pass
41 41 |
32 32 |
33 33 |
34 34 | class TestClass:
35 |- @pytest.mark.foo
35 |+ @pytest.mark.foo()
36 36 | class TestNestedClass:
37 37 | def test_something():
38 38 | pass
PT023.py:43:9: PT023 [*] Use `@pytest.mark.foo()` over `@pytest.mark.foo`
|
41 | class TestClass:
42 | class TestNestedClass:
43 | @pytest.mark.foo
| ^^^^^^^^^^^^^^^^ PT023
44 | def test_something():
45 | pass
|
= help: Add parentheses
Safe fix
40 40 |
41 41 | class TestClass:
42 42 | class TestNestedClass:
43 |- @pytest.mark.foo
43 |+ @pytest.mark.foo()
44 44 | def test_something():
45 45 | pass
46 46 |