Skip to content

Commit b6ac272

Browse files
mbyrnepr2pre-commit-ci[bot]jacobtylerwallsPierre-Sassoulascdce8p
authored
Add new checker and bare-name-capture-pattern message (#10424)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jacob Walls <[email protected]> Co-authored-by: Pierre Sassoulas <[email protected]> Co-authored-by: Marc Mueller <[email protected]>
1 parent bbfcb38 commit b6ac272

File tree

11 files changed

+125
-1
lines changed

11 files changed

+125
-1
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ repos:
2828
- id: ruff-check
2929
name: ruff-doc
3030
files: doc/data/messages
31+
# Please exclude using doc/data/ruff.toml
32+
# exclude: "" # Leave empty
3133
- repo: https://github.com/Pierre-Sassoulas/copyright_notice_precommit
3234
rev: 0.1.2
3335
hooks:
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
red = 0
2+
green = 1
3+
blue = 2
4+
5+
6+
def func(color):
7+
match color:
8+
case red: # [bare-name-capture-pattern]
9+
print("I see red!")
10+
case green: # [bare-name-capture-pattern]
11+
print("Grass is green")
12+
case blue:
13+
print("I'm feeling the blues :(")
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from enum import Enum
2+
3+
4+
class Color(Enum):
5+
RED = 0
6+
GREEN = 1
7+
BLUE = 2
8+
9+
10+
def func(color: Color) -> None:
11+
match color:
12+
case Color.RED:
13+
print("I see red!")
14+
case Color.GREEN:
15+
print("Grass is green")
16+
case Color.BLUE:
17+
print("I'm feeling the blues :(")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `PEP 636 <https://peps.python.org/pep-0636/#matching-against-constants-and-enums>`_

doc/data/ruff.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ extend-exclude = [
66
"messages/d/duplicate-argument-name/bad.py",
77
"messages/s/syntax-error/bad.py",
88
# syntax error in newer python versions
9+
"messages/b/bare-name-capture-pattern/bad.py",
910
"messages/s/star-needs-assignment-target/bad.py",
10-
"messages/i/invalid-star-assignment-target/bad.py"
11+
"messages/i/invalid-star-assignment-target/bad.py",
1112
]
1213

1314
[lint]

doc/user_guide/checkers/features.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,18 @@ Logging checker Messages
676676
format-interpolation is disabled then you can use str.format.
677677

678678

679+
Match Statements checker
680+
~~~~~~~~~~~~~~~~~~~~~~~~
681+
682+
Verbatim name of the checker is ``match_statements``.
683+
684+
Match Statements checker Messages
685+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
686+
: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.*
687+
Emitted when a name capture pattern is used in a match statement and there
688+
are case statements below it.
689+
690+
679691
Method Args checker
680692
~~~~~~~~~~~~~~~~~~~
681693

doc/user_guide/messages/messages_overview.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ All messages in the error category:
6363
error/bad-str-strip-call
6464
error/bad-string-format-type
6565
error/bad-super-call
66+
error/bare-name-capture-pattern
6667
error/bidirectional-unicode
6768
error/broken-collections-callable
6869
error/broken-noreturn

doc/whatsnew/fragments/7128.new_check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Add ``match-statements`` checker and the following message:
2+
``bare-name-capture-pattern``.
3+
This will emit an error message when a name capture pattern is used in a match statement which would make the remaining patterns unreachable.
4+
This code is a SyntaxError at runtime.
5+
6+
Closes #7128
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2+
# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
3+
# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
4+
5+
"""Match statement checker for Python code."""
6+
7+
from __future__ import annotations
8+
9+
from typing import TYPE_CHECKING
10+
11+
from astroid import nodes
12+
13+
from pylint.checkers import BaseChecker
14+
from pylint.checkers.utils import only_required_for_messages
15+
from pylint.interfaces import HIGH
16+
17+
if TYPE_CHECKING:
18+
from pylint.lint import PyLinter
19+
20+
21+
class MatchStatementChecker(BaseChecker):
22+
name = "match_statements"
23+
msgs = {
24+
"E1901": (
25+
"The name capture `case %s` makes the remaining patterns unreachable. "
26+
"Use a dotted name (for example an enum) to fix this.",
27+
"bare-name-capture-pattern",
28+
"Emitted when a name capture pattern is used in a match statement "
29+
"and there are case statements below it.",
30+
)
31+
}
32+
33+
@only_required_for_messages("bare-name-capture-pattern")
34+
def visit_match(self, node: nodes.Match) -> None:
35+
"""Check if a name capture pattern prevents the other cases from being
36+
reached.
37+
"""
38+
for idx, case in enumerate(node.cases):
39+
match case.pattern:
40+
case nodes.MatchAs(pattern=None, name=nodes.AssignName()) if (
41+
idx < len(node.cases) - 1
42+
):
43+
self.add_message(
44+
"bare-name-capture-pattern",
45+
node=case.pattern,
46+
args=case.pattern.name.name,
47+
confidence=HIGH,
48+
)
49+
50+
51+
def register(linter: PyLinter) -> None:
52+
linter.register_checker(MatchStatementChecker(linter))
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Functional tests for the ``bare-name-capture-pattern`` message"""
2+
3+
4+
a = "a"
5+
b = "b"
6+
s = "a"
7+
8+
9+
match s:
10+
case a: # [bare-name-capture-pattern]
11+
pass
12+
case b: # [bare-name-capture-pattern]
13+
pass
14+
case "a" as some_name:
15+
pass
16+
case s:
17+
pass

0 commit comments

Comments
 (0)