diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pytest_style/PT006.py b/crates/ruff_linter/resources/test/fixtures/flake8_pytest_style/PT006.py index af6cb50d54505..d6cbcda604e26 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pytest_style/PT006.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pytest_style/PT006.py @@ -156,3 +156,46 @@ def test_invalid_argvalues(param): ------------------------------------------------ """ ... + + +# Regression tests for nested tuples that could cause syntax errors when unpacked. +# See: https://github.com/astral-sh/ruff/issues/22441 +@pytest.mark.parametrize( + ["param"], + [ + ((),), + ((1,),), + ], +) +def test_single_element_nested_empty_tuple(param): + ... + + +@pytest.mark.parametrize( + ["param"], + [ + ((1, 2),), + ((3, 4),), + ], +) +def test_single_element_nested_multi_tuple(param): + ... + + +@pytest.mark.parametrize( + ["param"], + [ + (((1,),),), + ], +) +def test_single_element_deeply_nested_tuple(param): + ... + + +@pytest.mark.parametrize( + ["param"], + [ + (((1,)),), + ], +) +def test_single_element_grouped_tuple(param): ... diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs index 904ca1c494dd2..6c72ef28404fe 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/parametrize.rs @@ -705,7 +705,8 @@ fn handle_single_name(checker: &Checker, argnames: &Expr, value: &Expr, argvalue // assert isinstance(x, int) # fails because `x` is a tuple, not an int // ``` let argvalues_edits = unpack_single_element_items(checker, argvalues); - let argnames_edit = Edit::range_replacement(checker.generator().expr(value), argnames.range()); + let argnames_edit = + Edit::range_replacement(unparse_expr_in_sequence(value, checker), argnames.range()); let fix = if checker.comment_ranges().intersects(argnames_edit.range()) || argvalues_edits .iter() @@ -743,13 +744,23 @@ fn unpack_single_element_items(checker: &Checker, expr: &Expr) -> Vec { } edits.push(Edit::range_replacement( - checker.generator().expr(elt), + unparse_expr_in_sequence(elt, checker), value.range(), )); } edits } +fn unparse_expr_in_sequence(expr: &Expr, checker: &Checker) -> String { + let content = checker.locator().slice(expr); + if let Expr::Tuple(tuple) = expr { + if !tuple.is_empty() && !tuple.parenthesized { + return format!("({content})"); + } + } + content.to_string() +} + fn handle_value_rows( checker: &Checker, elts: &[Expr], diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_csv.snap b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_csv.snap index ba76432fe169f..69f3a38d3631f 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_csv.snap +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_csv.snap @@ -252,3 +252,96 @@ help: Use a string for the first argument 143 | [ 144 | (1,), 145 | (2, 3), + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:164:5 + | +162 | # See: https://github.com/astral-sh/ruff/issues/22441 +163 | @pytest.mark.parametrize( +164 | ["param"], + | ^^^^^^^^^ +165 | [ +166 | ((),), + | +help: Use a string for the first argument +161 | # Regression tests for nested tuples that could cause syntax errors when unpacked. +162 | # See: https://github.com/astral-sh/ruff/issues/22441 +163 | @pytest.mark.parametrize( + - ["param"], +164 + "param", +165 | [ + - ((),), + - ((1,),), +166 + (), +167 + (1,), +168 | ], +169 | ) +170 | def test_single_element_nested_empty_tuple(param): + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:175:5 + | +174 | @pytest.mark.parametrize( +175 | ["param"], + | ^^^^^^^^^ +176 | [ +177 | ((1, 2),), + | +help: Use a string for the first argument +172 | +173 | +174 | @pytest.mark.parametrize( + - ["param"], +175 + "param", +176 | [ + - ((1, 2),), + - ((3, 4),), +177 + (1, 2), +178 + (3, 4), +179 | ], +180 | ) +181 | def test_single_element_nested_multi_tuple(param): + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:186:5 + | +185 | @pytest.mark.parametrize( +186 | ["param"], + | ^^^^^^^^^ +187 | [ +188 | (((1,),),), + | +help: Use a string for the first argument +183 | +184 | +185 | @pytest.mark.parametrize( + - ["param"], +186 + "param", +187 | [ + - (((1,),),), +188 + ((1,),), +189 | ], +190 | ) +191 | def test_single_element_deeply_nested_tuple(param): + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:196:5 + | +195 | @pytest.mark.parametrize( +196 | ["param"], + | ^^^^^^^^^ +197 | [ +198 | (((1,)),), + | +help: Use a string for the first argument +193 | +194 | +195 | @pytest.mark.parametrize( + - ["param"], +196 + "param", +197 | [ + - (((1,)),), +198 + (1,), +199 | ], +200 | ) +201 | def test_single_element_grouped_tuple(param): ... diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_default.snap b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_default.snap index 518ee826db78e..6aed32ccc7e1b 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_default.snap +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_default.snap @@ -422,3 +422,96 @@ help: Use a string for the first argument 143 | [ 144 | (1,), 145 | (2, 3), + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:164:5 + | +162 | # See: https://github.com/astral-sh/ruff/issues/22441 +163 | @pytest.mark.parametrize( +164 | ["param"], + | ^^^^^^^^^ +165 | [ +166 | ((),), + | +help: Use a string for the first argument +161 | # Regression tests for nested tuples that could cause syntax errors when unpacked. +162 | # See: https://github.com/astral-sh/ruff/issues/22441 +163 | @pytest.mark.parametrize( + - ["param"], +164 + "param", +165 | [ + - ((),), + - ((1,),), +166 + (), +167 + (1,), +168 | ], +169 | ) +170 | def test_single_element_nested_empty_tuple(param): + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:175:5 + | +174 | @pytest.mark.parametrize( +175 | ["param"], + | ^^^^^^^^^ +176 | [ +177 | ((1, 2),), + | +help: Use a string for the first argument +172 | +173 | +174 | @pytest.mark.parametrize( + - ["param"], +175 + "param", +176 | [ + - ((1, 2),), + - ((3, 4),), +177 + (1, 2), +178 + (3, 4), +179 | ], +180 | ) +181 | def test_single_element_nested_multi_tuple(param): + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:186:5 + | +185 | @pytest.mark.parametrize( +186 | ["param"], + | ^^^^^^^^^ +187 | [ +188 | (((1,),),), + | +help: Use a string for the first argument +183 | +184 | +185 | @pytest.mark.parametrize( + - ["param"], +186 + "param", +187 | [ + - (((1,),),), +188 + ((1,),), +189 | ], +190 | ) +191 | def test_single_element_deeply_nested_tuple(param): + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:196:5 + | +195 | @pytest.mark.parametrize( +196 | ["param"], + | ^^^^^^^^^ +197 | [ +198 | (((1,)),), + | +help: Use a string for the first argument +193 | +194 | +195 | @pytest.mark.parametrize( + - ["param"], +196 + "param", +197 | [ + - (((1,)),), +198 + (1,), +199 | ], +200 | ) +201 | def test_single_element_grouped_tuple(param): ... diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_list.snap b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_list.snap index 9c6fa17b1f670..a0729b05d94f3 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_list.snap +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT006_list.snap @@ -384,3 +384,96 @@ help: Use a string for the first argument 143 | [ 144 | (1,), 145 | (2, 3), + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:164:5 + | +162 | # See: https://github.com/astral-sh/ruff/issues/22441 +163 | @pytest.mark.parametrize( +164 | ["param"], + | ^^^^^^^^^ +165 | [ +166 | ((),), + | +help: Use a string for the first argument +161 | # Regression tests for nested tuples that could cause syntax errors when unpacked. +162 | # See: https://github.com/astral-sh/ruff/issues/22441 +163 | @pytest.mark.parametrize( + - ["param"], +164 + "param", +165 | [ + - ((),), + - ((1,),), +166 + (), +167 + (1,), +168 | ], +169 | ) +170 | def test_single_element_nested_empty_tuple(param): + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:175:5 + | +174 | @pytest.mark.parametrize( +175 | ["param"], + | ^^^^^^^^^ +176 | [ +177 | ((1, 2),), + | +help: Use a string for the first argument +172 | +173 | +174 | @pytest.mark.parametrize( + - ["param"], +175 + "param", +176 | [ + - ((1, 2),), + - ((3, 4),), +177 + (1, 2), +178 + (3, 4), +179 | ], +180 | ) +181 | def test_single_element_nested_multi_tuple(param): + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:186:5 + | +185 | @pytest.mark.parametrize( +186 | ["param"], + | ^^^^^^^^^ +187 | [ +188 | (((1,),),), + | +help: Use a string for the first argument +183 | +184 | +185 | @pytest.mark.parametrize( + - ["param"], +186 + "param", +187 | [ + - (((1,),),), +188 + ((1,),), +189 | ], +190 | ) +191 | def test_single_element_deeply_nested_tuple(param): + +PT006 [*] Wrong type passed to first argument of `pytest.mark.parametrize`; expected `str` + --> PT006.py:196:5 + | +195 | @pytest.mark.parametrize( +196 | ["param"], + | ^^^^^^^^^ +197 | [ +198 | (((1,)),), + | +help: Use a string for the first argument +193 | +194 | +195 | @pytest.mark.parametrize( + - ["param"], +196 + "param", +197 | [ + - (((1,)),), +198 + (1,), +199 | ], +200 | ) +201 | def test_single_element_grouped_tuple(param): ...