From 31aca81de57c61d298e7328f5913aba09a1ea70e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 22 Oct 2023 12:45:07 +0000 Subject: [PATCH] Fixes duplicate-code check with ignore-imports (#9147) (#9176) (cherry picked from commit 67f20bd380a9e788b7f58bb121668e9dae7cdbd1) Co-authored-by: theirix --- doc/whatsnew/fragments/8914.bugfix | 3 +++ pylint/checkers/similar.py | 21 ++++++------------- tests/checkers/unittest_similar.py | 7 ++++++- .../ignore_conditional_imports/__init__.py | 0 .../ignore_conditional_imports/file_one.py | 6 ++++++ .../ignore_conditional_imports/file_two.py | 6 ++++++ tests/test_similar.py | 15 +++++++++++++ 7 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 doc/whatsnew/fragments/8914.bugfix create mode 100644 tests/regrtest_data/duplicate_code/ignore_conditional_imports/__init__.py create mode 100644 tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_one.py create mode 100644 tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_two.py diff --git a/doc/whatsnew/fragments/8914.bugfix b/doc/whatsnew/fragments/8914.bugfix new file mode 100644 index 0000000000..90a2be6b47 --- /dev/null +++ b/doc/whatsnew/fragments/8914.bugfix @@ -0,0 +1,3 @@ +Fixes ignoring conditional imports with ``ignore-imports=y``. + +Closes #8914 diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index fc50ed4153..b4091631ad 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -41,7 +41,7 @@ from collections.abc import Callable, Generator, Iterable, Sequence from getopt import getopt from io import BufferedIOBase, BufferedReader, BytesIO -from itertools import chain, groupby +from itertools import chain from typing import ( TYPE_CHECKING, Any, @@ -598,17 +598,10 @@ def stripped_lines( if ignore_imports or ignore_signatures: tree = astroid.parse("".join(lines)) if ignore_imports: - node_is_import_by_lineno = ( - (node.lineno, isinstance(node, (nodes.Import, nodes.ImportFrom))) - for node in tree.body - ) - line_begins_import = { - lineno: all(is_import for _, is_import in node_is_import_group) - for lineno, node_is_import_group in groupby( - node_is_import_by_lineno, key=lambda x: x[0] - ) - } - current_line_is_import = False + import_lines = {} + for node in tree.nodes_of_class((nodes.Import, nodes.ImportFrom)): + for lineno in range(node.lineno, (node.end_lineno or node.lineno) + 1): + import_lines[lineno] = True if ignore_signatures: def _get_functions( @@ -664,9 +657,7 @@ def _get_functions( docstring = None line = "" if ignore_imports: - current_line_is_import = line_begins_import.get( - lineno, current_line_is_import - ) + current_line_is_import = import_lines.get(lineno, False) if current_line_is_import: line = "" if ignore_comments: diff --git a/tests/checkers/unittest_similar.py b/tests/checkers/unittest_similar.py index f5bfc6fb20..8c00faee54 100644 --- a/tests/checkers/unittest_similar.py +++ b/tests/checkers/unittest_similar.py @@ -9,6 +9,7 @@ import pytest from pylint.checkers import similar +from pylint.constants import IS_PYPY, PY39_PLUS from pylint.lint import PyLinter from pylint.testutils import GenericTestReporter as Reporter @@ -130,6 +131,10 @@ def test_multiline_imports() -> None: ) +@pytest.mark.skipif( + IS_PYPY and not PY39_PLUS, + reason="Requires accurate 'end_lineno' value", +) def test_ignore_multiline_imports() -> None: output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: @@ -295,7 +300,7 @@ def test_no_hide_code_with_imports() -> None: with redirect_stdout(output), pytest.raises(SystemExit) as ex: similar.Run(["--ignore-imports"] + 2 * [HIDE_CODE_WITH_IMPORTS]) assert ex.value.code == 0 - assert "TOTAL lines=32 duplicates=16 percent=50.00" in output.getvalue() + assert "TOTAL lines=32 duplicates=0 percent=0.00" in output.getvalue() def test_ignore_nothing() -> None: diff --git a/tests/regrtest_data/duplicate_code/ignore_conditional_imports/__init__.py b/tests/regrtest_data/duplicate_code/ignore_conditional_imports/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_one.py b/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_one.py new file mode 100644 index 0000000000..9856cd5c77 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_one.py @@ -0,0 +1,6 @@ +import typing + +x = 1 +if typing.TYPE_CHECKING: + import os + import sys diff --git a/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_two.py b/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_two.py new file mode 100644 index 0000000000..9856cd5c77 --- /dev/null +++ b/tests/regrtest_data/duplicate_code/ignore_conditional_imports/file_two.py @@ -0,0 +1,6 @@ +import typing + +x = 1 +if typing.TYPE_CHECKING: + import os + import sys diff --git a/tests/test_similar.py b/tests/test_similar.py index ca58342c1d..8a09b45008 100644 --- a/tests/test_similar.py +++ b/tests/test_similar.py @@ -264,3 +264,18 @@ def test_useless_suppression() -> None: exit=False, ) assert not runner.linter.stats.by_msg + + def test_conditional_imports(self) -> None: + """Tests enabling ignore-imports with conditional imports works correctly.""" + path = join(DATA, "ignore_conditional_imports") + expected_output = "==ignore_conditional_imports.file_one:[2:4]" + self._test_output( + [ + path, + "-e=duplicate-code", + "-d=unused-import,C", + "--ignore-imports=y", + "--min-similarity-lines=1", + ], + expected_output=expected_output, + )