diff options
author | Maksym Humetskyi <Humetsky@gmail.com> | 2021-08-17 21:54:44 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-17 20:54:44 +0200 |
commit | ef72cdc6f9b96ea680d6a9d0f37861f80d6bc3db (patch) | |
tree | a74dec167eccd9cf5b244a07cc685561e26ef500 | |
parent | 676f484871a40bd0256e1cc68c28ea748a61acec (diff) | |
download | pylint-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.txt | 1 | ||||
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | doc/whatsnew/2.10.rst | 4 | ||||
-rw-r--r-- | pylint/checkers/similar.py | 23 | ||||
-rw-r--r-- | tests/checkers/unittest_similar.py | 62 | ||||
-rw-r--r-- | tests/input/similar_cls_a.py | 27 | ||||
-rw-r--r-- | tests/input/similar_cls_b.py | 27 |
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 @@ -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 |