diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ced4d5d..81dd0b29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +* Basic `match` statement support (kreathon, #276). + # 2.5 (2022-07-03) * Mark imports in `__all__` as used (kreathon, #172, #282). diff --git a/tests/test_scavenging.py b/tests/test_scavenging.py index 6fb57295..83739061 100644 --- a/tests/test_scavenging.py +++ b/tests/test_scavenging.py @@ -1,5 +1,7 @@ import sys +import pytest + from . import check, v assert v # Silence pyflakes. @@ -759,3 +761,102 @@ def foo(a, b, c, d=3): check(v.used_names, ["foo", "a", "b", "c", "d"]) check(v.unused_vars, []) check(v.unused_funcs, []) + + +@pytest.mark.skipif( + sys.version_info < (3, 10), reason="requires python3.10 or higher" +) +def test_match_class_simple(v): + v.scan( + """\ +from dataclasses import dataclass + + +@dataclass +class X: + a: int + b: int + c: int + u: int + +x = input() + +match x: + case X(a=0): + print("a") + case X(b=0, c=0): + print("b c") +""" + ) + check(v.defined_classes, ["X"]) + check(v.defined_vars, ["a", "b", "c", "u", "x"]) + + check(v.unused_classes, []) + check(v.unused_vars, ["u"]) + + +@pytest.mark.skipif( + sys.version_info < (3, 10), reason="requires python3.10 or higher" +) +def test_match_class_embedded(v): + v.scan( + """\ +from dataclasses import dataclass + + +@dataclass +class X: + a: int + b: int + c: int + d: int + e: int + u: int + +x = input() + +match x: + case X(a=1) | X(b=0): + print("Or") + case [X(c=1), X(d=0)]: + print("Sequence") + case {"k": X(e=1)}: + print("Mapping") +""" + ) + check(v.defined_classes, ["X"]) + check(v.defined_vars, ["a", "b", "c", "d", "e", "u", "x"]) + + check(v.unused_classes, []) + check(v.unused_vars, ["u"]) + + +@pytest.mark.skipif( + sys.version_info < (3, 10), reason="requires python3.10 or higher" +) +def test_match_enum(v): + v.scan( + """\ +from enum import Enum + + +class Color(Enum): + RED = 0 + YELLOW = 1 + GREEN = 2 + BLUE = 3 + +color = input() + +match color: + case Color.RED: + print("Real danger!") + case Color.YELLOW | Color.GREEN: + print("No danger!") +""" + ) + check(v.defined_classes, ["Color"]) + check(v.defined_vars, ["RED", "YELLOW", "GREEN", "BLUE", "color"]) + + check(v.unused_classes, []) + check(v.unused_vars, ["BLUE"]) diff --git a/vulture/core.py b/vulture/core.py index 3f716f72..96a8ed6d 100644 --- a/vulture/core.py +++ b/vulture/core.py @@ -635,6 +635,10 @@ def visit_Assign(self, node): def visit_While(self, node): self._handle_conditional_node(node, "while") + def visit_MatchClass(self, node): + for kwd_attr in node.kwd_attrs: + self.used_names.add(kwd_attr) + def visit(self, node): method = "visit_" + node.__class__.__name__ visitor = getattr(self, method, None) diff --git a/vulture/whitelists/ast_whitelist.py b/vulture/whitelists/ast_whitelist.py index d3dd843a..258dca47 100644 --- a/vulture/whitelists/ast_whitelist.py +++ b/vulture/whitelists/ast_whitelist.py @@ -39,6 +39,7 @@ whitelist_node_visitor.visit_Lambda whitelist_node_visitor.visit_List whitelist_node_visitor.visit_ListComp +whitelist_node_visitor.visit_MatchClass whitelist_node_visitor.visit_Module whitelist_node_visitor.visit_Name whitelist_node_visitor.visit_NameConstant