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
65 changes: 32 additions & 33 deletions pylint/checkers/async_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from typing import TYPE_CHECKING

import astroid
from astroid import nodes, util
from astroid import nodes

from pylint import checkers
from pylint.checkers import utils as checker_utils
Expand Down Expand Up @@ -54,39 +54,38 @@ def visit_asyncfunctiondef(self, node: nodes.AsyncFunctionDef) -> None:
@checker_utils.only_required_for_messages("not-async-context-manager")
def visit_asyncwith(self, node: nodes.AsyncWith) -> None:
for ctx_mgr, _ in node.items:
inferred = checker_utils.safe_infer(ctx_mgr)
if inferred is None or isinstance(inferred, util.UninferableBase):
continue

if isinstance(inferred, nodes.AsyncFunctionDef):
# Check if we are dealing with a function decorated
# with contextlib.asynccontextmanager.
if decorated_with(inferred, self._async_generators):
continue
elif isinstance(inferred, astroid.bases.AsyncGenerator):
# Check if we are dealing with a function decorated
# with contextlib.asynccontextmanager.
if decorated_with(inferred.parent, self._async_generators):
continue
else:
try:
inferred.getattr("__aenter__")
inferred.getattr("__aexit__")
except astroid.exceptions.NotFoundError:
if isinstance(inferred, astroid.Instance):
# If we do not know the bases of this class,
# just skip it.
if not checker_utils.has_known_bases(inferred):
continue
# Ignore mixin classes if they match the rgx option.
if (
"not-async-context-manager"
in self.linter.config.ignored_checks_for_mixins
and self._mixin_class_rgx.match(inferred.name)
):
continue
else:
match inferred := checker_utils.safe_infer(ctx_mgr):
case _ if not inferred:
continue
case nodes.AsyncFunctionDef():
# Check if we are dealing with a function decorated
# with contextlib.asynccontextmanager.
if decorated_with(inferred, self._async_generators):
continue
case astroid.bases.AsyncGenerator():
# Check if we are dealing with a function decorated
# with contextlib.asynccontextmanager.
if decorated_with(inferred.parent, self._async_generators):
continue
case _:
try:
inferred.getattr("__aenter__")
inferred.getattr("__aexit__")
except astroid.exceptions.NotFoundError:
if isinstance(inferred, astroid.Instance):
# If we do not know the bases of this class,
# just skip it.
if not checker_utils.has_known_bases(inferred):
continue
# Ignore mixin classes if they match the rgx option.
if (
"not-async-context-manager"
in self.linter.config.ignored_checks_for_mixins
and self._mixin_class_rgx.match(inferred.name)
):
continue
else:
continue
self.add_message(
"not-async-context-manager", node=node, args=(inferred.name,)
)
Expand Down
30 changes: 16 additions & 14 deletions pylint/checkers/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,25 +447,27 @@ def _check_catching_non_exception(
# Don't emit the warning if the inferred stmt
# is None, but the exception handler is something else,
# maybe it was redefined.
if isinstance(exc, nodes.Const) and exc.value is None:
if (
isinstance(handler.type, nodes.Const) and handler.type.value is None
) or handler.type.parent_of(exc):
# If the exception handler catches None or
# the exception component, which is None, is
# defined by the entire exception handler, then
# emit a warning.
match exc:
case nodes.Const(value=None):
if (
isinstance(handler.type, nodes.Const)
and handler.type.value is None
) or handler.type.parent_of(exc):
# If the exception handler catches None or
# the exception component, which is None, is
# defined by the entire exception handler, then
# emit a warning.
self.add_message(
"catching-non-exception",
node=handler.type,
args=(part.as_string(),),
)
case _:
self.add_message(
"catching-non-exception",
node=handler.type,
args=(part.as_string(),),
)
else:
self.add_message(
"catching-non-exception",
node=handler.type,
args=(part.as_string(),),
)
return

if (
Expand Down
162 changes: 81 additions & 81 deletions pylint/checkers/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def new_line(self, tokens: TokenWrapper, line_end: int, line_start: int) -> None
def process_module(self, node: nodes.Module) -> None:
pass

# pylint: disable-next = too-many-return-statements, too-many-branches
# pylint: disable-next = too-many-return-statements
def _check_keyword_parentheses(
self, tokens: list[tokenize.TokenInfo], start: int
) -> None:
Expand Down Expand Up @@ -347,30 +347,31 @@ def _check_keyword_parentheses(
)
return
elif depth == 1:
# This is a tuple, which is always acceptable.
if token[1] == ",":
return
# 'and' and 'or' are the only boolean operators with lower precedence
# than 'not', so parens are only required when they are found.
if token[1] in {"and", "or"}:
found_and_or = True
# A yield inside an expression must always be in parentheses,
# quit early without error.
elif token[1] == "yield":
return
# A generator expression always has a 'for' token in it, and
# the 'for' token is only legal inside parens when it is in a
# generator expression. The parens are necessary here, so bail
# without an error.
elif token[1] == "for":
return
# A generator expression can have an 'else' token in it.
# We check the rest of the tokens to see if any problems occur after
# the 'else'.
elif token[1] == "else":
if "(" in (i.string for i in tokens[i:]):
self._check_keyword_parentheses(tokens[i:], 0)
return
match token[1]:
case ",":
# This is a tuple, which is always acceptable.
return
case "and" | "or":
# 'and' and 'or' are the only boolean operators with lower precedence
# than 'not', so parens are only required when they are found.
found_and_or = True
case "yield":
# A yield inside an expression must always be in parentheses,
# quit early without error.
return
case "for":
# A generator expression always has a 'for' token in it, and
# the 'for' token is only legal inside parens when it is in a
# generator expression. The parens are necessary here, so bail
# without an error.
return
case "else":
# A generator expression can have an 'else' token in it.
# We check the rest of the tokens to see if any problems occur after
# the 'else'.
if "(" in (i.string for i in tokens[i:]):
self._check_keyword_parentheses(tokens[i:], 0)
return

def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None:
"""Process tokens and search for:
Expand All @@ -397,39 +398,42 @@ def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None:
else:
self.new_line(TokenWrapper(tokens), idx - 1, idx)

if tok_type == tokenize.NEWLINE:
# a program statement, or ENDMARKER, will eventually follow,
# after some (possibly empty) run of tokens of the form
# (NL | COMMENT)* (INDENT | DEDENT+)?
# If an INDENT appears, setting check_equal is wrong, and will
# be undone when we see the INDENT.
check_equal = True
self._check_line_ending(string, line_num)
elif tok_type == tokenize.INDENT:
check_equal = False
self.check_indent_level(string, indents[-1] + 1, line_num)
indents.append(indents[-1] + 1)
elif tok_type == tokenize.DEDENT:
# there's nothing we need to check here! what's important is
# that when the run of DEDENTs ends, the indentation of the
# program statement (or ENDMARKER) that triggered the run is
# equal to what's left at the top of the indents stack
check_equal = True
if len(indents) > 1:
del indents[-1]
elif tok_type == tokenize.NL:
if not line.strip("\r\n"):
last_blank_line_num = line_num
elif tok_type not in (tokenize.COMMENT, tokenize.ENCODING):
# This is the first concrete token following a NEWLINE, so it
# must be the first token of the next program statement, or an
# ENDMARKER; the "line" argument exposes the leading white-space
# for this statement; in the case of ENDMARKER, line is an empty
# string, so will properly match the empty string with which the
# "indents" stack was seeded
if check_equal:
match tok_type:
case tokenize.NEWLINE:
# a program statement, or ENDMARKER, will eventually follow,
# after some (possibly empty) run of tokens of the form
# (NL | COMMENT)* (INDENT | DEDENT+)?
# If an INDENT appears, setting check_equal is wrong, and will
# be undone when we see the INDENT.
check_equal = True
self._check_line_ending(string, line_num)
case tokenize.INDENT:
check_equal = False
self.check_indent_level(line, indents[-1], line_num)
self.check_indent_level(string, indents[-1] + 1, line_num)
indents.append(indents[-1] + 1)
case tokenize.DEDENT:
# there's nothing we need to check here! what's important is
# that when the run of DEDENTs ends, the indentation of the
# program statement (or ENDMARKER) that triggered the run is
# equal to what's left at the top of the indents stack
check_equal = True
if len(indents) > 1:
del indents[-1]
case tokenize.NL:
if not line.strip("\r\n"):
last_blank_line_num = line_num
case tokenize.COMMENT | tokenize.ENCODING:
pass
case _:
# This is the first concrete token following a NEWLINE, so it
# must be the first token of the next program statement, or an
# ENDMARKER; the "line" argument exposes the leading white-space
# for this statement; in the case of ENDMARKER, line is an empty
# string, so will properly match the empty string with which the
# "indents" stack was seeded
if check_equal:
check_equal = False
self.check_indent_level(line, indents[-1], line_num)

if tok_type == tokenize.NUMBER and string.endswith("l"):
self.add_message("lowercase-l-suffix", line=line_num)
Expand Down Expand Up @@ -546,30 +550,26 @@ def _infer_else_finally_line_number(

def _check_multi_statement_line(self, node: nodes.NodeNG, line: int) -> None:
"""Check for lines containing multiple statements."""
if isinstance(node, nodes.With):
# Do not warn about multiple nested context managers in with statements.
return
if (
isinstance(node.parent, nodes.If)
and not node.parent.orelse
and self.linter.config.single_line_if_stmt
):
return
if (
isinstance(node.parent, nodes.ClassDef)
and len(node.parent.body) == 1
and self.linter.config.single_line_class_stmt
):
return

# Functions stubs and class with ``Ellipsis`` as body are exempted.
if (
isinstance(node, nodes.Expr)
and isinstance(node.parent, (nodes.FunctionDef, nodes.ClassDef))
and isinstance(node.value, nodes.Const)
and node.value.value is Ellipsis
):
return
match node:
case nodes.With():
# Do not warn about multiple nested context managers in with statements.
return
case nodes.NodeNG(
parent=nodes.If(orelse=[])
) if self.linter.config.single_line_if_stmt:
return
case nodes.NodeNG(
parent=nodes.ClassDef(body=[_])
) if self.linter.config.single_line_class_stmt:
return
case nodes.Expr(
parent=nodes.FunctionDef() | nodes.ClassDef(),
value=nodes.Const(value=value),
) if (
value is Ellipsis
):
# Functions stubs and class with ``Ellipsis`` as body are exempted.
return

self.add_message("multiple-statements", node=node, confidence=HIGH)
self._visited_lines[line] = 2
Expand Down
Loading
Loading