diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5df487e4f6..f09ebec2ae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,6 +28,8 @@ repos: - id: ruff-check name: ruff-doc files: doc/data/messages + # Please exclude using doc/data/ruff.toml + # exclude: "" # Leave empty - repo: https://github.com/Pierre-Sassoulas/copyright_notice_precommit rev: 0.1.2 hooks: diff --git a/doc/data/messages/b/bare-name-capture-pattern/bad.py b/doc/data/messages/b/bare-name-capture-pattern/bad.py new file mode 100644 index 0000000000..f5c73a6899 --- /dev/null +++ b/doc/data/messages/b/bare-name-capture-pattern/bad.py @@ -0,0 +1,13 @@ +red = 0 +green = 1 +blue = 2 + + +color = blue +match color: + case red: # [bare-name-capture-pattern] + print("I see red!") + case green: # [bare-name-capture-pattern] + print("Grass is green") + case blue: + print("I'm feeling the blues :(") diff --git a/doc/data/messages/b/bare-name-capture-pattern/good.py b/doc/data/messages/b/bare-name-capture-pattern/good.py new file mode 100644 index 0000000000..0278641b89 --- /dev/null +++ b/doc/data/messages/b/bare-name-capture-pattern/good.py @@ -0,0 +1,17 @@ +from enum import Enum + + +class Color(Enum): + RED = 0 + GREEN = 1 + BLUE = 2 + + +color = Color.BLUE +match color: + case Color.RED: + print("I see red!") + case Color.GREEN: + print("Grass is green") + case Color.BLUE: + print("I'm feeling the blues :(") diff --git a/doc/data/messages/b/bare-name-capture-pattern/related.rst b/doc/data/messages/b/bare-name-capture-pattern/related.rst new file mode 100644 index 0000000000..bb0fe8f41e --- /dev/null +++ b/doc/data/messages/b/bare-name-capture-pattern/related.rst @@ -0,0 +1 @@ +- `PEP 636 `_ diff --git a/doc/data/ruff.toml b/doc/data/ruff.toml index 23fd5eec46..8011fc4ca5 100644 --- a/doc/data/ruff.toml +++ b/doc/data/ruff.toml @@ -6,8 +6,9 @@ extend-exclude = [ "messages/d/duplicate-argument-name/bad.py", "messages/s/syntax-error/bad.py", # syntax error in newer python versions + "messages/b/bare-name-capture-pattern/bad.py", # python 3.10 "messages/s/star-needs-assignment-target/bad.py", - "messages/i/invalid-star-assignment-target/bad.py" + "messages/i/invalid-star-assignment-target/bad.py", ] [lint] diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index 670b7d67ec..7f758a047c 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -673,6 +673,18 @@ Logging checker Messages format-interpolation is disabled then you can use str.format. +Match Statements checker +~~~~~~~~~~~~~~~~~~~~~~~~ + +Verbatim name of the checker is ``match_statements``. + +Match Statements checker Messages +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:bare-name-capture-pattern (E1901): *The name capture `case %s` makes the remaining patterns unreachable. Use a dotted name (for example an enum) to fix this.* + Emitted when a name capture pattern is used in a match statement and there + are case statements below it. + + Method Args checker ~~~~~~~~~~~~~~~~~~~ diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst index fc487fc25c..2d068bcb76 100644 --- a/doc/user_guide/messages/messages_overview.rst +++ b/doc/user_guide/messages/messages_overview.rst @@ -63,6 +63,7 @@ All messages in the error category: error/bad-str-strip-call error/bad-string-format-type error/bad-super-call + error/bare-name-capture-pattern error/bidirectional-unicode error/broken-collections-callable error/broken-noreturn diff --git a/doc/whatsnew/fragments/7128.new_check b/doc/whatsnew/fragments/7128.new_check new file mode 100644 index 0000000000..4f39a588f8 --- /dev/null +++ b/doc/whatsnew/fragments/7128.new_check @@ -0,0 +1,6 @@ +Add ``match-statements`` checker and the following message: +``bare-name-capture-pattern``. +This will emit an error message when a name capture pattern is used in a match statement which would make the remaining patterns unreachable. +This code is a SyntaxError at runtime. + +Closes #7128 diff --git a/pylint/checkers/match_statements_checker.py b/pylint/checkers/match_statements_checker.py new file mode 100644 index 0000000000..ed84fb7e1b --- /dev/null +++ b/pylint/checkers/match_statements_checker.py @@ -0,0 +1,54 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt + +"""Match statement checker for Python code.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from astroid import nodes + +from pylint.checkers import BaseChecker +from pylint.checkers.utils import only_required_for_messages +from pylint.interfaces import HIGH + +if TYPE_CHECKING: + from pylint.lint import PyLinter + + +class MatchStatementChecker(BaseChecker): + name = "match_statements" + msgs = { + "E1901": ( + "The name capture `case %s` makes the remaining patterns unreachable. " + "Use a dotted name (for example an enum) to fix this.", + "bare-name-capture-pattern", + "Emitted when a name capture pattern is used in a match statement " + "and there are case statements below it.", + ) + } + + @only_required_for_messages("bare-name-capture-pattern") + def visit_match(self, node: nodes.Match) -> None: + """Check if a name capture pattern prevents the other cases from being + reached. + """ + for idx, case in enumerate(node.cases): + if ( + isinstance(case.pattern, nodes.MatchAs) + and case.pattern.pattern is None + and isinstance(case.pattern.name, nodes.AssignName) + and idx < len(node.cases) - 1 + ): + self.add_message( + "bare-name-capture-pattern", + node=case, + args=case.pattern.name.name, + confidence=HIGH, + ) + + +def register(linter: PyLinter) -> None: + linter.register_checker(MatchStatementChecker(linter)) diff --git a/tests/functional/b/bare_name_capture_pattern.py b/tests/functional/b/bare_name_capture_pattern.py new file mode 100644 index 0000000000..d656da33d6 --- /dev/null +++ b/tests/functional/b/bare_name_capture_pattern.py @@ -0,0 +1,15 @@ +"""Functional tests for the ``bare-name-capture-pattern`` message""" + + +a = 'a' +b = 'b' +s = 'a' + + +match s: + case a: # [bare-name-capture-pattern] + pass + case b: # [bare-name-capture-pattern] + pass + case s: + pass diff --git a/tests/functional/b/bare_name_capture_pattern.txt b/tests/functional/b/bare_name_capture_pattern.txt new file mode 100644 index 0000000000..699672b8c9 --- /dev/null +++ b/tests/functional/b/bare_name_capture_pattern.txt @@ -0,0 +1,2 @@ +bare-name-capture-pattern:10:0:None:None::The name capture `case a` makes the remaining patterns unreachable. Use a dotted name (for example an enum) to fix this.:HIGH +bare-name-capture-pattern:12:0:None:None::The name capture `case b` makes the remaining patterns unreachable. Use a dotted name (for example an enum) to fix this.:HIGH