Skip to content

Commit 300cb79

Browse files
Use match statement in checkers (2) (#10528)
Co-authored-by: Pierre Sassoulas <[email protected]>
1 parent df3aa2a commit 300cb79

15 files changed

+837
-802
lines changed

pylint/checkers/async_checker.py

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from typing import TYPE_CHECKING
1111

1212
import astroid
13-
from astroid import nodes, util
13+
from astroid import nodes
1414

1515
from pylint import checkers
1616
from pylint.checkers import utils as checker_utils
@@ -54,39 +54,38 @@ def visit_asyncfunctiondef(self, node: nodes.AsyncFunctionDef) -> None:
5454
@checker_utils.only_required_for_messages("not-async-context-manager")
5555
def visit_asyncwith(self, node: nodes.AsyncWith) -> None:
5656
for ctx_mgr, _ in node.items:
57-
inferred = checker_utils.safe_infer(ctx_mgr)
58-
if inferred is None or isinstance(inferred, util.UninferableBase):
59-
continue
60-
61-
if isinstance(inferred, nodes.AsyncFunctionDef):
62-
# Check if we are dealing with a function decorated
63-
# with contextlib.asynccontextmanager.
64-
if decorated_with(inferred, self._async_generators):
65-
continue
66-
elif isinstance(inferred, astroid.bases.AsyncGenerator):
67-
# Check if we are dealing with a function decorated
68-
# with contextlib.asynccontextmanager.
69-
if decorated_with(inferred.parent, self._async_generators):
70-
continue
71-
else:
72-
try:
73-
inferred.getattr("__aenter__")
74-
inferred.getattr("__aexit__")
75-
except astroid.exceptions.NotFoundError:
76-
if isinstance(inferred, astroid.Instance):
77-
# If we do not know the bases of this class,
78-
# just skip it.
79-
if not checker_utils.has_known_bases(inferred):
80-
continue
81-
# Ignore mixin classes if they match the rgx option.
82-
if (
83-
"not-async-context-manager"
84-
in self.linter.config.ignored_checks_for_mixins
85-
and self._mixin_class_rgx.match(inferred.name)
86-
):
87-
continue
88-
else:
57+
match inferred := checker_utils.safe_infer(ctx_mgr):
58+
case _ if not inferred:
8959
continue
60+
case nodes.AsyncFunctionDef():
61+
# Check if we are dealing with a function decorated
62+
# with contextlib.asynccontextmanager.
63+
if decorated_with(inferred, self._async_generators):
64+
continue
65+
case astroid.bases.AsyncGenerator():
66+
# Check if we are dealing with a function decorated
67+
# with contextlib.asynccontextmanager.
68+
if decorated_with(inferred.parent, self._async_generators):
69+
continue
70+
case _:
71+
try:
72+
inferred.getattr("__aenter__")
73+
inferred.getattr("__aexit__")
74+
except astroid.exceptions.NotFoundError:
75+
if isinstance(inferred, astroid.Instance):
76+
# If we do not know the bases of this class,
77+
# just skip it.
78+
if not checker_utils.has_known_bases(inferred):
79+
continue
80+
# Ignore mixin classes if they match the rgx option.
81+
if (
82+
"not-async-context-manager"
83+
in self.linter.config.ignored_checks_for_mixins
84+
and self._mixin_class_rgx.match(inferred.name)
85+
):
86+
continue
87+
else:
88+
continue
9089
self.add_message(
9190
"not-async-context-manager", node=node, args=(inferred.name,)
9291
)

pylint/checkers/exceptions.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -447,25 +447,27 @@ def _check_catching_non_exception(
447447
# Don't emit the warning if the inferred stmt
448448
# is None, but the exception handler is something else,
449449
# maybe it was redefined.
450-
if isinstance(exc, nodes.Const) and exc.value is None:
451-
if (
452-
isinstance(handler.type, nodes.Const) and handler.type.value is None
453-
) or handler.type.parent_of(exc):
454-
# If the exception handler catches None or
455-
# the exception component, which is None, is
456-
# defined by the entire exception handler, then
457-
# emit a warning.
450+
match exc:
451+
case nodes.Const(value=None):
452+
if (
453+
isinstance(handler.type, nodes.Const)
454+
and handler.type.value is None
455+
) or handler.type.parent_of(exc):
456+
# If the exception handler catches None or
457+
# the exception component, which is None, is
458+
# defined by the entire exception handler, then
459+
# emit a warning.
460+
self.add_message(
461+
"catching-non-exception",
462+
node=handler.type,
463+
args=(part.as_string(),),
464+
)
465+
case _:
458466
self.add_message(
459467
"catching-non-exception",
460468
node=handler.type,
461469
args=(part.as_string(),),
462470
)
463-
else:
464-
self.add_message(
465-
"catching-non-exception",
466-
node=handler.type,
467-
args=(part.as_string(),),
468-
)
469471
return
470472

471473
if (

pylint/checkers/format.py

Lines changed: 81 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def new_line(self, tokens: TokenWrapper, line_end: int, line_start: int) -> None
272272
def process_module(self, node: nodes.Module) -> None:
273273
pass
274274

275-
# pylint: disable-next = too-many-return-statements, too-many-branches
275+
# pylint: disable-next = too-many-return-statements
276276
def _check_keyword_parentheses(
277277
self, tokens: list[tokenize.TokenInfo], start: int
278278
) -> None:
@@ -351,30 +351,31 @@ def _check_keyword_parentheses(
351351
)
352352
return
353353
elif depth == 1:
354-
# This is a tuple, which is always acceptable.
355-
if token[1] == ",":
356-
return
357-
# 'and' and 'or' are the only boolean operators with lower precedence
358-
# than 'not', so parens are only required when they are found.
359-
if token[1] in {"and", "or"}:
360-
found_and_or = True
361-
# A yield inside an expression must always be in parentheses,
362-
# quit early without error.
363-
elif token[1] == "yield":
364-
return
365-
# A generator expression always has a 'for' token in it, and
366-
# the 'for' token is only legal inside parens when it is in a
367-
# generator expression. The parens are necessary here, so bail
368-
# without an error.
369-
elif token[1] == "for":
370-
return
371-
# A generator expression can have an 'else' token in it.
372-
# We check the rest of the tokens to see if any problems occur after
373-
# the 'else'.
374-
elif token[1] == "else":
375-
if "(" in (i.string for i in tokens[i:]):
376-
self._check_keyword_parentheses(tokens[i:], 0)
377-
return
354+
match token[1]:
355+
case ",":
356+
# This is a tuple, which is always acceptable.
357+
return
358+
case "and" | "or":
359+
# 'and' and 'or' are the only boolean operators with lower precedence
360+
# than 'not', so parens are only required when they are found.
361+
found_and_or = True
362+
case "yield":
363+
# A yield inside an expression must always be in parentheses,
364+
# quit early without error.
365+
return
366+
case "for":
367+
# A generator expression always has a 'for' token in it, and
368+
# the 'for' token is only legal inside parens when it is in a
369+
# generator expression. The parens are necessary here, so bail
370+
# without an error.
371+
return
372+
case "else":
373+
# A generator expression can have an 'else' token in it.
374+
# We check the rest of the tokens to see if any problems occur after
375+
# the 'else'.
376+
if "(" in (i.string for i in tokens[i:]):
377+
self._check_keyword_parentheses(tokens[i:], 0)
378+
return
378379

379380
def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None:
380381
"""Process tokens and search for:
@@ -401,39 +402,42 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None:
401402
else:
402403
self.new_line(TokenWrapper(tokens), idx - 1, idx)
403404

404-
if tok_type == tokenize.NEWLINE:
405-
# a program statement, or ENDMARKER, will eventually follow,
406-
# after some (possibly empty) run of tokens of the form
407-
# (NL | COMMENT)* (INDENT | DEDENT+)?
408-
# If an INDENT appears, setting check_equal is wrong, and will
409-
# be undone when we see the INDENT.
410-
check_equal = True
411-
self._check_line_ending(string, line_num)
412-
elif tok_type == tokenize.INDENT:
413-
check_equal = False
414-
self.check_indent_level(string, indents[-1] + 1, line_num)
415-
indents.append(indents[-1] + 1)
416-
elif tok_type == tokenize.DEDENT:
417-
# there's nothing we need to check here! what's important is
418-
# that when the run of DEDENTs ends, the indentation of the
419-
# program statement (or ENDMARKER) that triggered the run is
420-
# equal to what's left at the top of the indents stack
421-
check_equal = True
422-
if len(indents) > 1:
423-
del indents[-1]
424-
elif tok_type == tokenize.NL:
425-
if not line.strip("\r\n"):
426-
last_blank_line_num = line_num
427-
elif tok_type not in (tokenize.COMMENT, tokenize.ENCODING):
428-
# This is the first concrete token following a NEWLINE, so it
429-
# must be the first token of the next program statement, or an
430-
# ENDMARKER; the "line" argument exposes the leading white-space
431-
# for this statement; in the case of ENDMARKER, line is an empty
432-
# string, so will properly match the empty string with which the
433-
# "indents" stack was seeded
434-
if check_equal:
405+
match tok_type:
406+
case tokenize.NEWLINE:
407+
# a program statement, or ENDMARKER, will eventually follow,
408+
# after some (possibly empty) run of tokens of the form
409+
# (NL | COMMENT)* (INDENT | DEDENT+)?
410+
# If an INDENT appears, setting check_equal is wrong, and will
411+
# be undone when we see the INDENT.
412+
check_equal = True
413+
self._check_line_ending(string, line_num)
414+
case tokenize.INDENT:
435415
check_equal = False
436-
self.check_indent_level(line, indents[-1], line_num)
416+
self.check_indent_level(string, indents[-1] + 1, line_num)
417+
indents.append(indents[-1] + 1)
418+
case tokenize.DEDENT:
419+
# there's nothing we need to check here! what's important is
420+
# that when the run of DEDENTs ends, the indentation of the
421+
# program statement (or ENDMARKER) that triggered the run is
422+
# equal to what's left at the top of the indents stack
423+
check_equal = True
424+
if len(indents) > 1:
425+
del indents[-1]
426+
case tokenize.NL:
427+
if not line.strip("\r\n"):
428+
last_blank_line_num = line_num
429+
case tokenize.COMMENT | tokenize.ENCODING:
430+
pass
431+
case _:
432+
# This is the first concrete token following a NEWLINE, so it
433+
# must be the first token of the next program statement, or an
434+
# ENDMARKER; the "line" argument exposes the leading white-space
435+
# for this statement; in the case of ENDMARKER, line is an empty
436+
# string, so will properly match the empty string with which the
437+
# "indents" stack was seeded
438+
if check_equal:
439+
check_equal = False
440+
self.check_indent_level(line, indents[-1], line_num)
437441

438442
if tok_type == tokenize.NUMBER and string.endswith("l"):
439443
self.add_message("lowercase-l-suffix", line=line_num)
@@ -550,30 +554,26 @@ def _infer_else_finally_line_number(
550554

551555
def _check_multi_statement_line(self, node: nodes.NodeNG, line: int) -> None:
552556
"""Check for lines containing multiple statements."""
553-
if isinstance(node, nodes.With):
554-
# Do not warn about multiple nested context managers in with statements.
555-
return
556-
if (
557-
isinstance(node.parent, nodes.If)
558-
and not node.parent.orelse
559-
and self.linter.config.single_line_if_stmt
560-
):
561-
return
562-
if (
563-
isinstance(node.parent, nodes.ClassDef)
564-
and len(node.parent.body) == 1
565-
and self.linter.config.single_line_class_stmt
566-
):
567-
return
568-
569-
# Functions stubs and class with ``Ellipsis`` as body are exempted.
570-
if (
571-
isinstance(node, nodes.Expr)
572-
and isinstance(node.parent, (nodes.FunctionDef, nodes.ClassDef))
573-
and isinstance(node.value, nodes.Const)
574-
and node.value.value is Ellipsis
575-
):
576-
return
557+
match node:
558+
case nodes.With():
559+
# Do not warn about multiple nested context managers in with statements.
560+
return
561+
case nodes.NodeNG(
562+
parent=nodes.If(orelse=[])
563+
) if self.linter.config.single_line_if_stmt:
564+
return
565+
case nodes.NodeNG(
566+
parent=nodes.ClassDef(body=[_])
567+
) if self.linter.config.single_line_class_stmt:
568+
return
569+
case nodes.Expr(
570+
parent=nodes.FunctionDef() | nodes.ClassDef(),
571+
value=nodes.Const(value=value),
572+
) if (
573+
value is Ellipsis
574+
):
575+
# Functions stubs and class with ``Ellipsis`` as body are exempted.
576+
return
577577

578578
self.add_message("multiple-statements", node=node, confidence=HIGH)
579579
self._visited_lines[line] = 2

0 commit comments

Comments
 (0)