diff options
author | Scott Worley <scottworley@scottworley.com> | 2018-08-19 15:17:20 -0700 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2018-09-29 15:42:50 +0200 |
commit | 76ce7c13b4a3059cdad08ccd9264c3d52a6683c2 (patch) | |
tree | 0179d9c432e9e3650806620b9c071fcbe805368d | |
parent | c9f6087f910604579d6bfca70df3e812dbcdd304 (diff) | |
download | pylint-git-76ce7c13b4a3059cdad08ccd9264c3d52a6683c2.tar.gz |
Use ast to handle PEP 328 multi-line imports
Closes #1422
Closes #2019
This fixes #1422 and fixes #2019.
-rw-r--r-- | CONTRIBUTORS.txt | 2 | ||||
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | pylint/checkers/similar.py | 21 | ||||
-rw-r--r-- | pylint/test/input/hide_code_with_imports.py | 16 | ||||
-rw-r--r-- | pylint/test/input/multiline-import | 8 | ||||
-rw-r--r-- | pylint/test/unittest_checker_similar.py | 47 |
6 files changed, 93 insertions, 5 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 9fb8aeb17..729d92144 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -237,3 +237,5 @@ contributors: * Tomer Chachamu, Richard Goodman: simplifiable-if-expression * Benjamin Drung: contributing Debian Developer + +* Scott Worley: contributor @@ -140,6 +140,10 @@ Release date: TBA Close #2430 + * Fix --ignore-imports to understand multi-line imports + + Close #1422 + Close #2019 What's New in Pylint 2.1.1? =========================== diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py index 369d0ce37..da2ab5b9d 100644 --- a/pylint/checkers/similar.py +++ b/pylint/checkers/similar.py @@ -20,6 +20,9 @@ from __future__ import print_function import sys from collections import defaultdict +from itertools import groupby + +import astroid from pylint.utils import decoding_stream from pylint.interfaces import IRawChecker @@ -153,10 +156,20 @@ def stripped_lines(lines, ignore_comments, ignore_docstrings, ignore_imports): """return lines with leading/trailing whitespace and any ignored code features removed """ + if ignore_imports: + tree = astroid.parse(''.join(lines)) + node_is_import_by_lineno = ( + (node.lineno, isinstance(node, (astroid.Import, astroid.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 strippedlines = [] docstring = None - for line in lines: + for lineno, line in enumerate(lines, start=1): line = line.strip() if ignore_docstrings: if not docstring and (line.startswith('"""') or line.startswith("'''")): @@ -167,8 +180,10 @@ def stripped_lines(lines, ignore_comments, ignore_docstrings, ignore_imports): docstring = None line = "" if ignore_imports: - if line.startswith("import ") or line.startswith("from "): - line = "" + current_line_is_import = line_begins_import.get( + lineno, current_line_is_import) + if current_line_is_import: + line = '' if ignore_comments: # XXX should use regex in checkers/format to avoid cutting # at a "#" in a string diff --git a/pylint/test/input/hide_code_with_imports.py b/pylint/test/input/hide_code_with_imports.py new file mode 100644 index 000000000..a8449c9b9 --- /dev/null +++ b/pylint/test/input/hide_code_with_imports.py @@ -0,0 +1,16 @@ +quicksort = lambda a: qs1(a,0,len(a)-1) ; import a +qs1 = lambda a,lo,hi: qs2(a,lo,hi) if lo<hi else a ; import b +qs2 = lambda a,lo,hi: qs3(lo,hi,*qsp(a,lo,hi)) ; import c +qs3 = lambda lo,hi,a,p: qs1(qs1(a,p+1,hi),lo,p-1) ; import d +qsp = lambda a,lo,hi: qsp1(a,lo,hi,a[hi],lo,lo) ; import e +qsp1 = lambda a,lo,hi,p,i,j: qsp2(a,lo,hi,p,i,j,j<hi) ; import f +qsp2 = lambda a,lo,hi,p,i,j,c: qspt(a,lo,hi,p,i,j,qsp3 if c else qsp7); import g +qspt = lambda a,lo,hi,p,i,j,n: n(a,lo,hi,p,i,j) ; import h +qsp3 = lambda a,lo,hi,p,i,j: qsp4(a,lo,hi,p,i,j,a[j]<p) ; import i +qsp4 = lambda a,lo,hi,p,i,j,c: qspt(a,lo,hi,p,i,j,qsp5 if c else qsp6); import j +qsp5 = lambda a,lo,hi,p,i,j: qsp1(sw(a,i,j),lo,hi,p,i+1,j+1) ; import k +qsp6 = lambda a,lo,hi,p,i,j: qsp1(a,lo,hi,p,i,j+1) ; import l +qsp7 = lambda a,lo,hi,p,i,j: (sw(a,i,hi), i) ; import m +sw = lambda a,i,j: sw1(enumerate(a),i,j,a[i],(-1,a[j])) ; import n +sw1 = lambda a,i,j,ai,aj: sw2([aj if x[0]==i else x for x in a],j,ai) ; import o +sw2 = lambda a,j,ai: [ai if x[0]==j else x[1] for x in a] ; import p diff --git a/pylint/test/input/multiline-import b/pylint/test/input/multiline-import new file mode 100644 index 000000000..b9507ef8a --- /dev/null +++ b/pylint/test/input/multiline-import @@ -0,0 +1,8 @@ +from foo import ( + bar, + baz, + quux, + quuux, + quuuux, + quuuuux, +) diff --git a/pylint/test/unittest_checker_similar.py b/pylint/test/unittest_checker_similar.py index bf4842cea..4151419eb 100644 --- a/pylint/test/unittest_checker_similar.py +++ b/pylint/test/unittest_checker_similar.py @@ -18,8 +18,12 @@ import pytest from pylint.checkers import similar -SIMILAR1 = join(dirname(abspath(__file__)), "input", "similar1") -SIMILAR2 = join(dirname(abspath(__file__)), "input", "similar2") +SIMILAR1 = join(dirname(abspath(__file__)), 'input', 'similar1') +SIMILAR2 = join(dirname(abspath(__file__)), 'input', 'similar2') +MULTILINE = join(dirname(abspath(__file__)), 'input', 'multiline-import') +HIDE_CODE_WITH_IMPORTS = join( + dirname(abspath(__file__)), 'input', 'hide_code_with_imports.py') + def test_ignore_comments(): @@ -100,6 +104,45 @@ TOTAL lines=44 duplicates=0 percent=0.00 ) +def test_multiline_imports(): + output = StringIO() + with redirect_stdout(output), pytest.raises(SystemExit) as ex: + similar.Run([MULTILINE, MULTILINE]) + assert ex.value.code == 0 + assert output.getvalue().strip() == (""" +8 similar lines in 2 files +==%s:0 +==%s:0 + from foo import ( + bar, + baz, + quux, + quuux, + quuuux, + quuuuux, + ) +TOTAL lines=16 duplicates=8 percent=50.00 +""" % (MULTILINE, MULTILINE)).strip() + + +def test_ignore_multiline_imports(): + output = StringIO() + with redirect_stdout(output), pytest.raises(SystemExit) as ex: + similar.Run(['--ignore-imports', MULTILINE, MULTILINE]) + assert ex.value.code == 0 + assert output.getvalue().strip() == """ +TOTAL lines=16 duplicates=0 percent=0.00 +""".strip() + + +def test_no_hide_code_with_imports(): + output = StringIO() + 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() + + def test_ignore_nothing(): output = StringIO() with redirect_stdout(output), pytest.raises(SystemExit) as ex: |