diff options
author | ttenhoeve-aa <ttenhoeve@appannie.com> | 2017-11-11 17:23:45 +0100 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2017-11-11 17:23:45 +0100 |
commit | cdfaa17515096e2a10ade3c187e9cea7fb4a5c09 (patch) | |
tree | d925257f53db35208701e354b15493e162de52bd | |
parent | 4dfe3285734fb27c734fe513c48a4adae51783ab (diff) | |
download | pylint-git-cdfaa17515096e2a10ade3c187e9cea7fb4a5c09.tar.gz |
Fix line counting for missing-docstring check in combination with docstring-min-length (#1672)
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | pylint/checkers/base.py | 6 | ||||
-rw-r--r-- | pylint/checkers/utils.py | 23 | ||||
-rw-r--r-- | pylint/test/unittest_checker_base.py | 24 | ||||
-rw-r--r-- | pylint/test/unittest_utils.py | 176 |
5 files changed, 227 insertions, 5 deletions
@@ -139,6 +139,9 @@ What's New in Pylint 1.8? mixing ``Args`` and ``Keyword Args`` in Google docstring. Close #1409 + * Fix ``missing-docstring`` false negatives when modules, classes, or methods + consist of compound statements that exceed the ``docstring-min-length`` + * Fix ``useless-else-on-loop`` false positives when break statements are deeply nested inside loop. Close #1661 diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 8182d6533..4d01bc1b9 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -35,6 +35,7 @@ from pylint import exceptions from pylint import interfaces from pylint.checkers import utils from pylint import reporters +from pylint.checkers.utils import get_node_last_lineno from pylint.reporters.ureports import nodes as reporter_nodes import pylint.utils as lint_utils @@ -1604,10 +1605,7 @@ class DocStringChecker(_BasicChecker): if docstring is None: if not report_missing: return - if node.body: - lines = node.body[-1].lineno - node.body[0].lineno + 1 - else: - lines = 0 + lines = get_node_last_lineno(node) - node.lineno if node_type == 'module' and not lines: # If the module has no body, there's no reason diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 2dd41b69b..8d0f5607c 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -878,3 +878,26 @@ def is_registered_in_singledispatch_function(node): return decorated_with(func_def, singledispatch_qnames) return False + + +def get_node_last_lineno(node): + """ + Get the last lineno of the given node. For a simple statement this will just be node.lineno, + but for a node that has child statements (e.g. a method) this will be the lineno of the last + child statement recursively. + """ + # 'finalbody' is always the last clause in a try statement, if present + if getattr(node, 'finalbody', False): + return get_node_last_lineno(node.finalbody[-1]) + # For if, while, and for statements 'orelse' is always the last clause. + # For try statements 'orelse' is the last in the absence of a 'finalbody' + if getattr(node, 'orelse', False): + return get_node_last_lineno(node.orelse[-1]) + # try statements have the 'handlers' last if there is no 'orelse' or 'finalbody' + if getattr(node, 'handlers', False): + return get_node_last_lineno(node.handlers[-1]) + # All compound statements have a 'body' + if getattr(node, 'body', False): + return get_node_last_lineno(node.body[-1]) + # Not a compound statement + return node.lineno diff --git a/pylint/test/unittest_checker_base.py b/pylint/test/unittest_checker_base.py index 58301b01b..5da19f899 100644 --- a/pylint/test/unittest_checker_base.py +++ b/pylint/test/unittest_checker_base.py @@ -55,6 +55,30 @@ class TestDocstring(CheckerTestCase): self.checker.visit_functiondef(func) @set_config(docstring_min_length=2) + def test_long_function_no_docstring(self): + func = astroid.extract_node(""" + def func(tion): + pass + pass + """) + message = Message('missing-docstring', node=func, args=('function',)) + with self.assertAddsMessages(message): + self.checker.visit_functiondef(func) + + @set_config(docstring_min_length=2) + def test_long_function_nested_statements_no_docstring(self): + func = astroid.extract_node(""" + def func(tion): + try: + pass + except: + pass + """) + message = Message('missing-docstring', node=func, args=('function',)) + with self.assertAddsMessages(message): + self.checker.visit_functiondef(func) + + @set_config(docstring_min_length=2) def test_function_no_docstring_by_name(self): func = astroid.extract_node(""" def __fun__(tion): diff --git a/pylint/test/unittest_utils.py b/pylint/test/unittest_utils.py index 8377e3f3b..e6d996e6e 100644 --- a/pylint/test/unittest_utils.py +++ b/pylint/test/unittest_utils.py @@ -14,7 +14,7 @@ import warnings import astroid from pylint import utils -from pylint.checkers.utils import check_messages +from pylint.checkers.utils import check_messages, get_node_last_lineno from pylint.exceptions import InvalidMessageError import pytest @@ -207,3 +207,177 @@ def test_decoding_stream_known_encoding(): binary_io = io.BytesIO(u'€'.encode('cp1252')) stream = utils.decoding_stream(binary_io, 'cp1252') assert stream.read() == u'€' + +class TestGetNodeLastLineno: + + def test_get_node_last_lineno_simple(self): + node = astroid.extract_node(""" + pass + """) + assert get_node_last_lineno(node) == 2 + + + def test_get_node_last_lineno_if_simple(self): + node = astroid.extract_node(""" + if True: + print(1) + pass + """) + assert get_node_last_lineno(node) == 4 + + + def test_get_node_last_lineno_if_elseif_else(self): + node = astroid.extract_node(""" + if True: + print(1) + elif False: + print(2) + else: + print(3) + """) + assert get_node_last_lineno(node) == 7 + + + def test_get_node_last_lineno_while(self): + node = astroid.extract_node(""" + while True: + print(1) + """) + assert get_node_last_lineno(node) == 3 + + + def test_get_node_last_lineno_while_else(self): + node = astroid.extract_node(""" + while True: + print(1) + else: + print(2) + """) + assert get_node_last_lineno(node) == 5 + + + def test_get_node_last_lineno_for(self): + node = astroid.extract_node(""" + for x in range(0, 5): + print(1) + """) + assert get_node_last_lineno(node) == 3 + + + def test_get_node_last_lineno_for_else(self): + node = astroid.extract_node(""" + for x in range(0, 5): + print(1) + else: + print(2) + """) + assert get_node_last_lineno(node) == 5 + + + def test_get_node_last_lineno_try(self): + node = astroid.extract_node(""" + try: + print(1) + except ValueError: + print(2) + except Exception: + print(3) + """) + assert get_node_last_lineno(node) == 7 + + + def test_get_node_last_lineno_try_except_else(self): + node = astroid.extract_node(""" + try: + print(1) + except Exception: + print(2) + print(3) + else: + print(4) + """) + assert get_node_last_lineno(node) == 8 + + + def test_get_node_last_lineno_try_except_finally(self): + node = astroid.extract_node(""" + try: + print(1) + except Exception: + print(2) + finally: + print(4) + """) + assert get_node_last_lineno(node) == 7 + + + def test_get_node_last_lineno_try_except_else_finally(self): + node = astroid.extract_node(""" + try: + print(1) + except Exception: + print(2) + else: + print(3) + finally: + print(4) + """) + assert get_node_last_lineno(node) == 9 + + + def test_get_node_last_lineno_with(self): + node = astroid.extract_node(""" + with x as y: + print(1) + pass + """) + assert get_node_last_lineno(node) == 4 + + + def test_get_node_last_lineno_method(self): + node = astroid.extract_node(""" + def x(a, b): + print(a, b) + pass + """) + assert get_node_last_lineno(node) == 4 + + + def test_get_node_last_lineno_decorator(self): + node = astroid.extract_node(""" + @decor() + def x(a, b): + print(a, b) + pass + """) + assert get_node_last_lineno(node) == 5 + + def test_get_node_last_lineno_class(self): + node = astroid.extract_node(""" + class C(object): + CONST = True + + def x(self, b): + print(b) + + def y(self): + pass + pass + """) + assert get_node_last_lineno(node) == 10 + + + def test_get_node_last_lineno_combined(self): + node = astroid.extract_node(""" + class C(object): + CONST = True + + def y(self): + try: + pass + except: + pass + finally: + pass + """) + assert get_node_last_lineno(node) == 11 |