summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Mueller <30130371+cdce8p@users.noreply.github.com>2021-08-30 08:33:54 +0200
committerGitHub <noreply@github.com>2021-08-30 08:33:54 +0200
commita754d8dd7f2eab2c6ee7e443948084f476fef296 (patch)
tree80848b4020cc146c206e2beb466e104aa9fffb34
parentb959ce54dfe5749e7da0fe79fbe205d64255b966 (diff)
downloadpylint-git-a754d8dd7f2eab2c6ee7e443948084f476fef296.tar.gz
Add `consider-using-assignment-expr` to `CodeStyleChecker` (#4876)
* Add global py-version config * Add typing-extensions as requirement for Python < 3.10 * Add consider-using-assignment-expr to CodeStyleChecker * Default to max-line-length Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r--ChangeLog25
-rw-r--r--doc/whatsnew/2.11.rst10
-rw-r--r--pylint/extensions/code_style.py169
-rw-r--r--pylint/extensions/typing.py85
-rw-r--r--pylint/lint/pylinter.py12
-rw-r--r--pylintrc6
-rw-r--r--setup.cfg1
-rw-r--r--tests/functional/ext/code_style/code_style_consider_using_assignment_expr.py148
-rw-r--r--tests/functional/ext/code_style/code_style_consider_using_assignment_expr.rc6
-rw-r--r--tests/functional/ext/code_style/code_style_consider_using_assignment_expr.txt18
10 files changed, 413 insertions, 67 deletions
diff --git a/ChangeLog b/ChangeLog
index d514a10be..9a96ad270 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -10,28 +10,40 @@ Release date: TBA
..
Put new features here and also in 'doc/whatsnew/2.11.rst'
+
+* Added ``py-version`` config key (if ``[MASTER]`` section). Used for version dependant checks.
+ Will default to whatever Python version pylint is executed with.
+
+* ``CodeStyleChecker``
+
+ * Added ``consider-using-assignment-expr``: Emitted when an assignment is directly followed by an if statement
+ and both can be combined by using an assignment expression ``:=``. Requires Python 3.8
+
+ Closes #4862
+
* Added ``consider-using-f-string``: Emitted when .format() or '%' is being used to format a string.
Closes #3592
+* Fix false positive for ``consider-using-with`` if a context manager is assigned to a
+ variable in different paths of control flow (e. g. if-else clause).
+
+ Closes #4751
+
What's New in Pylint 2.10.3?
============================
Release date: TBA
- * Fix false positive for ``consider-using-with`` if a context manager is assigned to a
- variable in different paths of control flow (e. g. if-else clause).
+..
+ Put bug fixes that should not wait for a new minor version here
- Closes #4751
What's New in Pylint 2.10.2?
============================
Release date: 2021-08-21
-..
- Put bug fixes that should not wait for a new minor version here
-
* We now use platformdirs instead of appdirs since the latter is not maintained.
Closes #4886
@@ -148,7 +160,6 @@ Release date: 2021-08-20
Closes #4042
-
* Refactor of ``--list-msgs`` & ``--list-msgs-enabled``: both options now show whether messages are emittable with the current interpreter.
Closes #4778
diff --git a/doc/whatsnew/2.11.rst b/doc/whatsnew/2.11.rst
index e9c05b909..bbb2a7b20 100644
--- a/doc/whatsnew/2.11.rst
+++ b/doc/whatsnew/2.11.rst
@@ -20,6 +20,16 @@ New checkers
Extensions
==========
+* ``CodeStyleChecker``
+
+ * Added ``consider-using-assignment-expr``: Emitted when an assignment is directly followed by an if statement
+ and both can be combined by using an assignment expression ``:=``. Requires Python 3.8
+
+ Closes #4862
+
Other Changes
=============
+
+* Added ``py-version`` config key (if ``[MASTER]`` section). Used for version dependant checks.
+ Will default to whatever Python version pylint is executed with.
diff --git a/pylint/extensions/code_style.py b/pylint/extensions/code_style.py
index 3f400fd18..0d8bfd164 100644
--- a/pylint/extensions/code_style.py
+++ b/pylint/extensions/code_style.py
@@ -1,4 +1,5 @@
-from typing import List, Set, Tuple, Type, Union, cast
+import sys
+from typing import List, Optional, Set, Tuple, Type, Union, cast
from astroid import nodes
@@ -6,6 +7,12 @@ from pylint.checkers import BaseChecker, utils
from pylint.checkers.utils import check_messages, safe_infer
from pylint.interfaces import IAstroidChecker
from pylint.lint import PyLinter
+from pylint.utils.utils import get_global_option
+
+if sys.version_info >= (3, 10):
+ from typing import TypeGuard
+else:
+ from typing_extensions import TypeGuard
class CodeStyleChecker(BaseChecker):
@@ -41,12 +48,42 @@ class CodeStyleChecker(BaseChecker):
"Emitted where an in-place defined ``list`` can be replaced by a ``tuple``. "
"Due to optimizations by CPython, there is no performance benefit from it.",
),
+ "R6103": (
+ "Use '%s' instead",
+ "consider-using-assignment-expr",
+ "Emitted when an if assignment is directly followed by an if statement and "
+ "both can be combined by using an assignment expression ``:=``. "
+ "Requires Python 3.8",
+ ),
}
+ options = (
+ (
+ "max-line-length-suggestions",
+ {
+ "type": "int",
+ "metavar": "<int>",
+ "help": (
+ "Max line length for which to sill emit suggestions. "
+ "Used to prevent optional suggestions which would get split "
+ "by a code formatter (e.g., black). "
+ "Will default to the setting for ``max-line-length``."
+ ),
+ },
+ ),
+ )
def __init__(self, linter: PyLinter) -> None:
"""Initialize checker instance."""
super().__init__(linter=linter)
+ def open(self) -> None:
+ py_version: Tuple[int, int] = get_global_option(self, "py-version") # type: ignore
+ self._py38_plus = py_version >= (3, 8)
+ self._max_length: int = ( # type: ignore
+ self.config.max_line_length_suggestions
+ or get_global_option(self, "max-line-length")
+ )
+
@check_messages("consider-using-namedtuple-or-dataclass")
def visit_dict(self, node: nodes.Dict) -> None:
self._check_dict_consider_namedtuple_dataclass(node)
@@ -61,6 +98,11 @@ class CodeStyleChecker(BaseChecker):
if isinstance(node.iter, nodes.List):
self.add_message("consider-using-tuple", node=node.iter)
+ @check_messages("consider-using-assignment-expr")
+ def visit_if(self, node: nodes.If) -> None:
+ if self._py38_plus:
+ self._check_consider_using_assignment_expr(node)
+
def _check_dict_consider_namedtuple_dataclass(self, node: nodes.Dict) -> None:
"""Check if dictionary values can be replaced by Namedtuple or Dataclass."""
if not (
@@ -135,6 +177,131 @@ class CodeStyleChecker(BaseChecker):
self.add_message("consider-using-namedtuple-or-dataclass", node=node)
return
+ def _check_consider_using_assignment_expr(self, node: nodes.If) -> None:
+ """Check if an assignment expression (walrus operator) can be used.
+
+ For example if an assignment is directly followed by an if statment:
+ >>> x = 2
+ >>> if x:
+ >>> ...
+
+ Can be replaced by:
+ >>> if (x := 2):
+ >>> ...
+
+ Note: Assignment expressions were added in Python 3.8
+ """
+ # Check if `node.test` contains a `Name` node
+ node_name: Optional[nodes.Name] = None
+ if isinstance(node.test, nodes.Name):
+ node_name = node.test
+ elif (
+ isinstance(node.test, nodes.UnaryOp)
+ and node.test.op == "not"
+ and isinstance(node.test.operand, nodes.Name)
+ ):
+ node_name = node.test.operand
+ elif (
+ isinstance(node.test, nodes.Compare)
+ and isinstance(node.test.left, nodes.Name)
+ and len(node.test.ops) == 1
+ ):
+ node_name = node.test.left
+ else:
+ return
+
+ # Make sure the previous node is an assignment to the same name
+ # used in `node.test`. Furthermore, ignore if assignment spans multiple lines.
+ prev_sibling = node.previous_sibling()
+ if CodeStyleChecker._check_prev_sibling_to_if_stmt(
+ prev_sibling, node_name.name
+ ):
+
+ # Check if match statement would be a better fit.
+ # I.e. multiple ifs that test the same name.
+ if CodeStyleChecker._check_ignore_assignment_expr_suggestion(
+ node, node_name.name
+ ):
+ return
+
+ # Build suggestion string. Check length of suggestion
+ # does not exceed max-line-length-suggestions
+ test_str = node.test.as_string().replace(
+ node_name.name,
+ f"({node_name.name} := {prev_sibling.value.as_string()})",
+ 1,
+ )
+ suggestion = f"if {test_str}:"
+ if (
+ node.col_offset is not None
+ and len(suggestion) + node.col_offset > self._max_length
+ or len(suggestion) > self._max_length
+ ):
+ return
+
+ self.add_message(
+ "consider-using-assignment-expr",
+ node=node_name,
+ args=(suggestion,),
+ )
+
+ @staticmethod
+ def _check_prev_sibling_to_if_stmt(
+ prev_sibling: Optional[nodes.NodeNG], name: Optional[str]
+ ) -> TypeGuard[Union[nodes.Assign, nodes.AnnAssign]]:
+ """Check if previous sibling is an assignment with the same name.
+ Ignore statements which span multiple lines.
+ """
+ if prev_sibling is None or prev_sibling.tolineno - prev_sibling.fromlineno != 0:
+ return False
+
+ if (
+ isinstance(prev_sibling, nodes.Assign)
+ and len(prev_sibling.targets) == 1
+ and isinstance(prev_sibling.targets[0], nodes.AssignName)
+ and prev_sibling.targets[0].name == name
+ ):
+ return True
+ if (
+ isinstance(prev_sibling, nodes.AnnAssign)
+ and isinstance(prev_sibling.target, nodes.AssignName)
+ and prev_sibling.target.name == name
+ ):
+ return True
+ return False
+
+ @staticmethod
+ def _check_ignore_assignment_expr_suggestion(
+ node: nodes.If, name: Optional[str]
+ ) -> bool:
+ """Return True if suggestion for assignment expr should be ignore.
+
+ E.g., in cases where a match statement would be a better fit
+ (multiple conditions).
+ """
+ if isinstance(node.test, nodes.Compare):
+ next_if_node: Optional[nodes.If] = None
+ next_sibling = node.next_sibling()
+ if len(node.orelse) == 1 and isinstance(node.orelse[0], nodes.If):
+ # elif block
+ next_if_node = node.orelse[0]
+ elif isinstance(next_sibling, nodes.If):
+ # separate if block
+ next_if_node = next_sibling
+
+ if ( # pylint: disable=too-many-boolean-expressions
+ next_if_node is not None
+ and (
+ isinstance(next_if_node.test, nodes.Compare)
+ and isinstance(next_if_node.test.left, nodes.Name)
+ and next_if_node.test.left.name == name
+ or isinstance(next_if_node.test, nodes.Name)
+ and next_if_node.test.name == name
+ )
+ ):
+ return True
+ return False
+
def register(linter: PyLinter) -> None:
linter.register_checker(CodeStyleChecker(linter))
diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py
index ce4e6cd01..cf0281f33 100644
--- a/pylint/extensions/typing.py
+++ b/pylint/extensions/typing.py
@@ -1,5 +1,4 @@
-from functools import lru_cache
-from typing import Dict, List, NamedTuple, Set, Union
+from typing import Dict, List, NamedTuple, Set, Tuple, Union
import astroid.bases
from astroid import nodes
@@ -12,6 +11,7 @@ from pylint.checkers.utils import (
)
from pylint.interfaces import IAstroidChecker
from pylint.lint import PyLinter
+from pylint.utils.utils import get_global_option
class TypingAlias(NamedTuple):
@@ -104,19 +104,6 @@ class TypingChecker(BaseChecker):
}
options = (
(
- "py-version",
- {
- "default": (3, 7),
- "type": "py_version",
- "metavar": "<py_version>",
- "help": (
- "Min Python version to use for typing related checks, "
- "e.g. ``3.7``. This should be equal to the min supported Python "
- "version of the project."
- ),
- },
- ),
- (
"runtime-typing",
{
"default": True,
@@ -135,49 +122,38 @@ class TypingChecker(BaseChecker):
),
)
+ _should_check_typing_alias: bool
+ """The use of type aliases (PEP 585) requires Python 3.9
+ or Python 3.7+ with postponed evaluation.
+ """
+
+ _should_check_alternative_union_syntax: bool
+ """The use of alternative union syntax (PEP 604) requires Python 3.10
+ or Python 3.7+ with postponed evaluation.
+ """
+
def __init__(self, linter: PyLinter) -> None:
"""Initialize checker instance."""
super().__init__(linter=linter)
self._alias_name_collisions: Set[str] = set()
self._consider_using_alias_msgs: List[DeprecatedTypingAliasMsg] = []
- @lru_cache()
- def _py37_plus(self) -> bool:
- return self.config.py_version >= (3, 7)
-
- @lru_cache()
- def _py39_plus(self) -> bool:
- return self.config.py_version >= (3, 9)
+ def open(self) -> None:
+ py_version: Tuple[int, int] = get_global_option(self, "py-version") # type: ignore
+ self._py37_plus = py_version >= (3, 7)
+ self._py39_plus = py_version >= (3, 9)
+ self._py310_plus = py_version >= (3, 10)
- @lru_cache()
- def _py310_plus(self) -> bool:
- return self.config.py_version >= (3, 10)
-
- @lru_cache()
- def _should_check_typing_alias(self) -> bool:
- """The use of type aliases (PEP 585) requires Python 3.9
- or Python 3.7+ with postponed evaluation.
- """
- return (
- self._py39_plus()
- or self._py37_plus()
- and self.config.runtime_typing is False
+ self._should_check_typing_alias = self._py39_plus or (
+ self._py37_plus and self.config.runtime_typing is False
)
-
- @lru_cache()
- def _should_check_alternative_union_syntax(self) -> bool:
- """The use of alternative union syntax (PEP 604) requires Python 3.10
- or Python 3.7+ with postponed evaluation.
- """
- return (
- self._py310_plus()
- or self._py37_plus()
- and self.config.runtime_typing is False
+ self._should_check_alternative_union_syntax = self._py310_plus or (
+ self._py37_plus and self.config.runtime_typing is False
)
def _msg_postponed_eval_hint(self, node) -> str:
"""Message hint if postponed evaluation isn't enabled."""
- if self._py310_plus() or "annotations" in node.root().future_imports:
+ if self._py310_plus or "annotations" in node.root().future_imports:
return ""
return ". Add 'from __future__ import annotations' as well"
@@ -187,9 +163,9 @@ class TypingChecker(BaseChecker):
"consider-alternative-union-syntax",
)
def visit_name(self, node: nodes.Name) -> None:
- if self._should_check_typing_alias() and node.name in ALIAS_NAMES:
+ if self._should_check_typing_alias and node.name in ALIAS_NAMES:
self._check_for_typing_alias(node)
- if self._should_check_alternative_union_syntax() and node.name in UNION_NAMES:
+ if self._should_check_alternative_union_syntax and node.name in UNION_NAMES:
self._check_for_alternative_union_syntax(node, node.name)
@check_messages(
@@ -198,12 +174,9 @@ class TypingChecker(BaseChecker):
"consider-alternative-union-syntax",
)
def visit_attribute(self, node: nodes.Attribute):
- if self._should_check_typing_alias() and node.attrname in ALIAS_NAMES:
+ if self._should_check_typing_alias and node.attrname in ALIAS_NAMES:
self._check_for_typing_alias(node)
- if (
- self._should_check_alternative_union_syntax()
- and node.attrname in UNION_NAMES
- ):
+ if self._should_check_alternative_union_syntax and node.attrname in UNION_NAMES:
self._check_for_alternative_union_syntax(node, node.attrname)
def _check_for_alternative_union_syntax(
@@ -230,7 +203,7 @@ class TypingChecker(BaseChecker):
and inferred.qname() == "typing._SpecialForm"
):
return
- if not (self._py310_plus() or is_node_in_type_annotation_context(node)):
+ if not (self._py310_plus or is_node_in_type_annotation_context(node)):
return
self.add_message(
"consider-alternative-union-syntax",
@@ -260,7 +233,7 @@ class TypingChecker(BaseChecker):
if alias is None:
return
- if self._py39_plus():
+ if self._py39_plus:
self.add_message(
"deprecated-typing-alias",
node=node,
@@ -290,7 +263,7 @@ class TypingChecker(BaseChecker):
'consider-using-alias' check. Make sure results are safe
to recommend / collision free.
"""
- if self._py37_plus() and not self._py39_plus():
+ if self._py37_plus and not self._py39_plus:
msg_future_import = self._msg_postponed_eval_hint(node)
for msg in self._consider_using_alias_msgs:
if msg.qname in self._alias_name_collisions:
diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py
index d09f4e39e..8582f7637 100644
--- a/pylint/lint/pylinter.py
+++ b/pylint/lint/pylinter.py
@@ -469,6 +469,18 @@ class PyLinter(
),
},
),
+ (
+ "py-version",
+ {
+ "default": sys.version_info[:2],
+ "type": "py_version",
+ "metavar": "<py_version>",
+ "help": (
+ "Min Python version to use for version dependend checks. "
+ "Will default to the version used to run pylint."
+ ),
+ },
+ ),
)
option_groups = (
diff --git a/pylintrc b/pylintrc
index 32dda6ca8..6acebfbfb 100644
--- a/pylintrc
+++ b/pylintrc
@@ -36,6 +36,9 @@ unsafe-load-any-extension=no
# run arbitrary code
extension-pkg-allow-list=
+# Minimum supported python version
+py-version = 3.6
+
[MESSAGES CONTROL]
@@ -383,9 +386,6 @@ overgeneral-exceptions=Exception
[TYPING]
-# Minimum supported python version (used for typing only!)
-py-version = 3.6
-
# Annotations are used exclusively for type checking
runtime-typing = no
diff --git a/setup.cfg b/setup.cfg
index 78527621f..e70a7317b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -49,6 +49,7 @@ install_requires =
mccabe>=0.6,<0.7
toml>=0.7.1
colorama;sys_platform=="win32"
+ typing-extensions>=3.10.0;python_version<"3.10"
python_requires = ~=3.6
[options.packages.find]
diff --git a/tests/functional/ext/code_style/code_style_consider_using_assignment_expr.py b/tests/functional/ext/code_style/code_style_consider_using_assignment_expr.py
new file mode 100644
index 000000000..07b51ce10
--- /dev/null
+++ b/tests/functional/ext/code_style/code_style_consider_using_assignment_expr.py
@@ -0,0 +1,148 @@
+# pylint: disable=missing-docstring,invalid-name,undefined-variable
+
+a1 = 2
+if a1: # [consider-using-assignment-expr]
+ ...
+
+# Do not suggest assignement expressions if assignment spans multiple lines
+a2 = (
+ 1,
+)
+if a2:
+ ...
+
+# Only first name should be replaced
+a3 = 2
+if a3 == a3_a: # [consider-using-assignment-expr]
+ ...
+
+# Above black line length
+a4 = some_loooooooonnnnnngggg_object_name.with_some_really_long_function_name(arg)
+if a4:
+ ...
+
+def func_a():
+ a5 = some___object.function_name_is_just_long_enough_to_fit_in_line() # some comment
+ if a5 is None: # [consider-using-assignment-expr]
+ ...
+
+ # Using assignment expression would result in line being 89 chars long
+ a6 = some_long_object.function_name_is_too_long_enough_to_fit___line()
+ if a6 is None:
+ ...
+
+# Previous unrelate note should not match
+print("")
+if a7:
+ ...
+
+
+b1: int = 2
+if b1: # [consider-using-assignment-expr]
+ ...
+
+b2 = some_function(2, 3)
+if b2: # [consider-using-assignment-expr]
+ ...
+
+b3 = some_object.variable
+if b3: # [consider-using-assignment-expr]
+ ...
+
+
+# UnaryOp
+c1 = 2
+if not c1: # [consider-using-assignment-expr]
+ ...
+
+
+# Compare
+d1 = 2
+if d1 is True: # [consider-using-assignment-expr]
+ ...
+
+d2 = 2
+if d2 is not None: # [consider-using-assignment-expr]
+ ...
+
+d3 = 2
+if d3 == 2: # [consider-using-assignment-expr]
+ ...
+
+
+# -----
+# Don't emit warning if match statement would be a better fit
+o1 = 2
+if o1 == 1:
+ ...
+elif o1 == 2:
+ ...
+elif o1 == 3:
+ ...
+
+o2 = 2
+if o2 == 1:
+ ...
+elif o2:
+ ...
+
+o3 = 2
+if o3 == 1: # [consider-using-assignment-expr]
+ ...
+else:
+ ...
+
+o4 = 2
+if o4 == 1: # [consider-using-assignment-expr]
+ ...
+elif o4 and o4_other:
+ ...
+
+o5 = 2
+if o5 == 1: # [consider-using-assignment-expr]
+ ...
+elif o5_other == 1:
+ ...
+
+o6 = 2
+if o6 == 1: # [consider-using-assignment-expr]
+ ...
+elif o6_other:
+ ...
+
+def func_p():
+ p1 = 2
+ if p1 == 1:
+ return
+ if p1 == 2:
+ return
+
+ p2 = 2
+ if p2 == 1:
+ return
+ if p2:
+ return
+
+ p3 = 2
+ if p3 == 1: # [consider-using-assignment-expr]
+ ...
+ else:
+ ...
+
+ p4 = 2
+ if p4 == 1: # [consider-using-assignment-expr]
+ ...
+ elif p4 and p4_other:
+ ...
+
+ p5 = 2
+ if p5 == 1: # [consider-using-assignment-expr]
+ ...
+ elif p5_other == 1:
+ ...
+
+ p6 = 2
+ if p6 == 1: # [consider-using-assignment-expr]
+ ...
+ elif p6_other:
+ ...
diff --git a/tests/functional/ext/code_style/code_style_consider_using_assignment_expr.rc b/tests/functional/ext/code_style/code_style_consider_using_assignment_expr.rc
new file mode 100644
index 000000000..2a659c07d
--- /dev/null
+++ b/tests/functional/ext/code_style/code_style_consider_using_assignment_expr.rc
@@ -0,0 +1,6 @@
+[MASTER]
+load-plugins=pylint.extensions.code_style
+py-version=3.8
+
+[CODE_STYLE]
+max-line-length-suggestions=88
diff --git a/tests/functional/ext/code_style/code_style_consider_using_assignment_expr.txt b/tests/functional/ext/code_style/code_style_consider_using_assignment_expr.txt
new file mode 100644
index 000000000..64179ba72
--- /dev/null
+++ b/tests/functional/ext/code_style/code_style_consider_using_assignment_expr.txt
@@ -0,0 +1,18 @@
+consider-using-assignment-expr:4:3::"Use 'if (a1 := 2):' instead":HIGH
+consider-using-assignment-expr:16:3::"Use 'if (a3 := 2) == a3_a:' instead":HIGH
+consider-using-assignment-expr:26:7:func_a:"Use 'if (a5 := some___object.function_name_is_just_long_enough_to_fit_in_line()) is None:' instead":HIGH
+consider-using-assignment-expr:41:3::"Use 'if (b1 := 2):' instead":HIGH
+consider-using-assignment-expr:45:3::"Use 'if (b2 := some_function(2, 3)):' instead":HIGH
+consider-using-assignment-expr:49:3::"Use 'if (b3 := some_object.variable):' instead":HIGH
+consider-using-assignment-expr:55:7::"Use 'if not (c1 := 2):' instead":HIGH
+consider-using-assignment-expr:61:3::"Use 'if (d1 := 2) is True:' instead":HIGH
+consider-using-assignment-expr:65:3::"Use 'if (d2 := 2) is not None:' instead":HIGH
+consider-using-assignment-expr:69:3::"Use 'if (d3 := 2) == 2:' instead":HIGH
+consider-using-assignment-expr:90:3::"Use 'if (o3 := 2) == 1:' instead":HIGH
+consider-using-assignment-expr:96:3::"Use 'if (o4 := 2) == 1:' instead":HIGH
+consider-using-assignment-expr:102:3::"Use 'if (o5 := 2) == 1:' instead":HIGH
+consider-using-assignment-expr:108:3::"Use 'if (o6 := 2) == 1:' instead":HIGH
+consider-using-assignment-expr:127:7:func_p:"Use 'if (p3 := 2) == 1:' instead":HIGH
+consider-using-assignment-expr:133:7:func_p:"Use 'if (p4 := 2) == 1:' instead":HIGH
+consider-using-assignment-expr:139:7:func_p:"Use 'if (p5 := 2) == 1:' instead":HIGH
+consider-using-assignment-expr:145:7:func_p:"Use 'if (p6 := 2) == 1:' instead":HIGH