summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaksym Humetskyi <Humetsky@gmail.com>2021-08-17 21:54:44 +0300
committerGitHub <noreply@github.com>2021-08-17 20:54:44 +0200
commitef72cdc6f9b96ea680d6a9d0f37861f80d6bc3db (patch)
treea74dec167eccd9cf5b244a07cc685561e26ef500
parent676f484871a40bd0256e1cc68c28ea748a61acec (diff)
downloadpylint-git-ef72cdc6f9b96ea680d6a9d0f37861f80d6bc3db.tar.gz
[duplicate-code] Parse functions and class methods recursively when gathering signature lines (#4858)
* [duplicate-code] Parse functions and class methods recursively when gathering signature lines
-rw-r--r--CONTRIBUTORS.txt1
-rw-r--r--ChangeLog5
-rw-r--r--doc/whatsnew/2.10.rst4
-rw-r--r--pylint/checkers/similar.py23
-rw-r--r--tests/checkers/unittest_similar.py62
-rw-r--r--tests/input/similar_cls_a.py27
-rw-r--r--tests/input/similar_cls_b.py27
7 files changed, 144 insertions, 5 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 80e68abb3..72d06f0a7 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -509,6 +509,7 @@ contributors:
* Maksym Humetskyi (mhumetskyi): contributor
- Fixed ignored empty functions by similarities checker with "ignore-signatures" option enabled
- Ignore function decorators signatures as well by similarities checker with "ignore-signatures" option enabled
+ - Ignore class methods and nested functions signatures as well by similarities checker with "ignore-signatures" option enabled
* Daniel Dorani (doranid): contributor
diff --git a/ChangeLog b/ChangeLog
index e5c4a5bae..6bd08a2d1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -150,6 +150,11 @@ Release date: TBA
* Allow ``true`` and ``false`` values in ``pylintrc`` for better compatibility with ``toml`` config.
+* Class methods' signatures are ignored the same way as functions' with similarities "ignore-signatures" option enabled
+
+ Closes #4653
+
+
What's New in Pylint 2.9.6?
===========================
Release date: 2021-07-28
diff --git a/doc/whatsnew/2.10.rst b/doc/whatsnew/2.10.rst
index 7f2706de4..e8a354a05 100644
--- a/doc/whatsnew/2.10.rst
+++ b/doc/whatsnew/2.10.rst
@@ -105,3 +105,7 @@ Other Changes
* Fixed bug with ``cell-var-from-loop`` checker: it no longer has false negatives when
both ``unused-variable`` and ``used-before-assignment`` are disabled.
+
+* Class methods' signatures are now ignored the same way as functions' with similarities "ignore-signatures" option enabled
+
+ Closes #4653
diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py
index 762ae35b2..b44081924 100644
--- a/pylint/checkers/similar.py
+++ b/pylint/checkers/similar.py
@@ -64,6 +64,7 @@ from typing import (
)
import astroid
+from astroid.node_classes import NodeNG
from pylint.checkers import BaseChecker, MapReduceMixin, table_lines_from_stats
from pylint.interfaces import IRawChecker
@@ -586,11 +587,23 @@ def stripped_lines(
}
current_line_is_import = False
if ignore_signatures:
- functions = [
- n
- for n in tree.body
- if isinstance(n, (astroid.FunctionDef, astroid.AsyncFunctionDef))
- ]
+
+ def _get_functions(functions: List[NodeNG], tree: NodeNG) -> List[NodeNG]:
+ """Recursively get all functions including nested in the classes from the tree."""
+
+ for node in tree.body:
+ if isinstance(node, (astroid.FunctionDef, astroid.AsyncFunctionDef)):
+ functions.append(node)
+
+ if isinstance(
+ node,
+ (astroid.ClassDef, astroid.FunctionDef, astroid.AsyncFunctionDef),
+ ):
+ _get_functions(functions, node)
+
+ return functions
+
+ functions = _get_functions([], tree)
signature_lines = set(
chain(
*(
diff --git a/tests/checkers/unittest_similar.py b/tests/checkers/unittest_similar.py
index ff3d92ddc..922fce654 100644
--- a/tests/checkers/unittest_similar.py
+++ b/tests/checkers/unittest_similar.py
@@ -36,6 +36,8 @@ SIMILAR3 = str(INPUT / "similar3")
SIMILAR4 = str(INPUT / "similar4")
SIMILAR5 = str(INPUT / "similar5")
SIMILAR6 = str(INPUT / "similar6")
+SIMILAR_CLS_A = str(INPUT / "similar_cls_a.py")
+SIMILAR_CLS_B = str(INPUT / "similar_cls_b.py")
EMPTY_FUNCTION_1 = str(INPUT / "similar_empty_func_1.py")
EMPTY_FUNCTION_2 = str(INPUT / "similar_empty_func_2.py")
MULTILINE = str(INPUT / "multiline-import")
@@ -213,6 +215,66 @@ TOTAL lines=35 duplicates=0 percent=0.00
)
+def test_ignore_signatures_class_methods_fail():
+ output = StringIO()
+ with redirect_stdout(output), pytest.raises(SystemExit) as ex:
+ similar.Run([SIMILAR_CLS_B, SIMILAR_CLS_A])
+ assert ex.value.code == 0
+ assert (
+ output.getvalue().strip()
+ == (
+ '''
+15 similar lines in 2 files
+==%s:[1:18]
+==%s:[1:18]
+ def parent_method(
+ self,
+ *,
+ a="",
+ b=None,
+ c=True,
+ ):
+ """Overridden method example."""
+
+ def _internal_func(
+ arg1: int = 1,
+ arg2: str = "2",
+ arg3: int = 3,
+ arg4: bool = True,
+ ):
+ pass
+
+
+7 similar lines in 2 files
+==%s:[20:27]
+==%s:[20:27]
+ self,
+ *,
+ a=None,
+ b=False,
+ c="",
+ ):
+ pass
+TOTAL lines=54 duplicates=22 percent=40.74
+'''
+ % (SIMILAR_CLS_A, SIMILAR_CLS_B, SIMILAR_CLS_A, SIMILAR_CLS_B)
+ ).strip()
+ )
+
+
+def test_ignore_signatures_class_methods_pass():
+ output = StringIO()
+ with redirect_stdout(output), pytest.raises(SystemExit) as ex:
+ similar.Run(["--ignore-signatures", SIMILAR_CLS_B, SIMILAR_CLS_A])
+ assert ex.value.code == 0
+ assert (
+ output.getvalue().strip()
+ == """
+TOTAL lines=54 duplicates=0 percent=0.00
+""".strip()
+ )
+
+
def test_ignore_signatures_empty_functions_fail():
output = StringIO()
with redirect_stdout(output), pytest.raises(SystemExit) as ex:
diff --git a/tests/input/similar_cls_a.py b/tests/input/similar_cls_a.py
new file mode 100644
index 000000000..9f4e149e2
--- /dev/null
+++ b/tests/input/similar_cls_a.py
@@ -0,0 +1,27 @@
+class A:
+ def parent_method(
+ self,
+ *,
+ a="",
+ b=None,
+ c=True,
+ ):
+ """Overridden method example."""
+
+ def _internal_func(
+ arg1: int = 1,
+ arg2: str = "2",
+ arg3: int = 3,
+ arg4: bool = True,
+ ):
+ pass
+
+ class InternalA:
+ def some_method_a(
+ self,
+ *,
+ a=None,
+ b=False,
+ c="",
+ ):
+ pass
diff --git a/tests/input/similar_cls_b.py b/tests/input/similar_cls_b.py
new file mode 100644
index 000000000..91df34a38
--- /dev/null
+++ b/tests/input/similar_cls_b.py
@@ -0,0 +1,27 @@
+class B:
+ def parent_method(
+ self,
+ *,
+ a="",
+ b=None,
+ c=True,
+ ):
+ """Overridden method example."""
+
+ def _internal_func(
+ arg1: int = 1,
+ arg2: str = "2",
+ arg3: int = 3,
+ arg4: bool = True,
+ ):
+ pass
+
+ class InternalB:
+ def some_method_b(
+ self,
+ *,
+ a=None,
+ b=False,
+ c="",
+ ):
+ pass