Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from wemake_python_styleguide.violations.consistency import (
SimplifiableIfMatchViolation,
SimplifiableMatchViolation,
)
from wemake_python_styleguide.visitors.ast.conditions import (
Expand Down Expand Up @@ -187,3 +188,111 @@ def test_not_simplifiable_match_templates(
visitor = SimplifiableMatchVisitor(default_options, tree=tree)
visitor.run()
assert_errors(visitor, [])


# New tests for single-case matches:

# Wrong (single case):
single_case_match = """
match x:
case {0}:
do_something()
"""

single_case_with_as_binding_match = """
match x:
case {0} as y:
do_something()
"""

# Correct (not single case or has guards):
multi_case_match = """
match x:
case 1:
do_something()
case 2:
do_something_else()
"""

single_case_with_guard = """
match x:
case y if y > 0:
do_something()
"""

wildcard_case = """
match x:
case _:
do_something()
"""

pattern_with_complex_structure = """
match x:
case [a, b]:
do_something()
"""

pattern_with_class_args = """
match x:
case SomeClass(a):
do_something()
"""


@pytest.mark.parametrize(
'code',
[
'1',
'True',
'None',
'"string"',
'ns.CONST',
'State.ACCEPTED',
],
)
def test_single_case_match(
code,
assert_errors,
parse_ast_tree,
default_options,
):
"""Test that single-case matches raise a violation."""
tree = parse_ast_tree(single_case_match.format(code))
visitor = SimplifiableMatchVisitor(default_options, tree=tree)
visitor.run()
assert_errors(visitor, [SimplifiableIfMatchViolation])


def test_single_case_with_as_binding(
assert_errors,
parse_ast_tree,
default_options,
):
"""Test that single-case matches with as-binding raise a violation."""
tree = parse_ast_tree(single_case_with_as_binding_match.format('1'))
visitor = SimplifiableMatchVisitor(default_options, tree=tree)
visitor.run()
assert_errors(visitor, [SimplifiableIfMatchViolation])


@pytest.mark.parametrize(
'template',
[
multi_case_match,
single_case_with_guard,
wildcard_case,
pattern_with_complex_structure,
pattern_with_class_args,
],
)
def test_not_single_case_match(
template,
assert_errors,
parse_ast_tree,
default_options,
):
"""Test that non-single-case matches do not raise the single-case violation."""
tree = parse_ast_tree(template)
visitor = SimplifiableMatchVisitor(default_options, tree=tree)
visitor.run()
assert_errors(visitor, [])
40 changes: 40 additions & 0 deletions wemake_python_styleguide/violations/consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -2460,3 +2460,43 @@ class SimplifiableMatchViolation(ASTViolation):
'Found simplifiable `match` statement that can be just `if`'
)
code = 365


@final
class SimplifiableIfMatchViolation(ASTViolation):
"""
Single-case ``match`` statements can be simplified to ``if`` statements.

Reasoning:
Using ``match`` for a single case is unnecessarily complex compared to a
simple ``if`` condition. The intent is clear, and using ``if`` reduces
nesting and cognitive load.

Solution:
Replace single-case ``match ... case`` statements with ``if``.

When is this violation is raised?
- When there is exactly one ``case`` statement
- When the pattern is simple (literal, constant, enum, etc.)
- When no guard (``if`` ...) is used
- When no wildcard pattern is used

Example::

# Correct:
if x == 1:
do_something()

# Wrong:
match x:
case 1:
do_something()

.. versionadded:: 1.10.0

"""

error_template = (
'Found single-case `match` statement that can be simplified to `if`'
)
code = 366
15 changes: 14 additions & 1 deletion wemake_python_styleguide/visitors/ast/conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,20 @@ def visit_Match(self, node: ast.Match) -> None:

def _check_simplifiable_match(self, node: ast.Match) -> None:
cases = node.cases
if len(cases) == 2:

# Check for single-case matches with no guard
if len(cases) == 1:
case = cases[0]
# Check if there's no guard condition
if case.guard is None:
# Check if the pattern is simple (literal, constant, enum, etc.)
if pattern_matching.is_simple_pattern(case.pattern):
self.add_violation(
consistency.SimplifiableIfMatchViolation(node)
)

# Check for two-case matches with wildcard (existing logic)
elif len(cases) == 2:
first, second = cases

if not pattern_matching.is_wildcard_pattern(second):
Expand Down