Skip to content

Commit cdfaa17

Browse files
ttenhoeve-aaPCManticore
authored andcommitted
Fix line counting for missing-docstring check in combination with docstring-min-length (#1672)
1 parent 4dfe328 commit cdfaa17

File tree

5 files changed

+227
-5
lines changed

5 files changed

+227
-5
lines changed

ChangeLog

+3
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ What's New in Pylint 1.8?
139139
mixing ``Args`` and ``Keyword Args`` in Google docstring.
140140
Close #1409
141141

142+
* Fix ``missing-docstring`` false negatives when modules, classes, or methods
143+
consist of compound statements that exceed the ``docstring-min-length``
144+
142145
* Fix ``useless-else-on-loop`` false positives when break statements are
143146
deeply nested inside loop.
144147
Close #1661

pylint/checkers/base.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from pylint import interfaces
3636
from pylint.checkers import utils
3737
from pylint import reporters
38+
from pylint.checkers.utils import get_node_last_lineno
3839
from pylint.reporters.ureports import nodes as reporter_nodes
3940
import pylint.utils as lint_utils
4041

@@ -1604,10 +1605,7 @@ def _check_docstring(self, node_type, node, report_missing=True,
16041605
if docstring is None:
16051606
if not report_missing:
16061607
return
1607-
if node.body:
1608-
lines = node.body[-1].lineno - node.body[0].lineno + 1
1609-
else:
1610-
lines = 0
1608+
lines = get_node_last_lineno(node) - node.lineno
16111609

16121610
if node_type == 'module' and not lines:
16131611
# If the module has no body, there's no reason

pylint/checkers/utils.py

+23
Original file line numberDiff line numberDiff line change
@@ -878,3 +878,26 @@ def is_registered_in_singledispatch_function(node):
878878
return decorated_with(func_def, singledispatch_qnames)
879879

880880
return False
881+
882+
883+
def get_node_last_lineno(node):
884+
"""
885+
Get the last lineno of the given node. For a simple statement this will just be node.lineno,
886+
but for a node that has child statements (e.g. a method) this will be the lineno of the last
887+
child statement recursively.
888+
"""
889+
# 'finalbody' is always the last clause in a try statement, if present
890+
if getattr(node, 'finalbody', False):
891+
return get_node_last_lineno(node.finalbody[-1])
892+
# For if, while, and for statements 'orelse' is always the last clause.
893+
# For try statements 'orelse' is the last in the absence of a 'finalbody'
894+
if getattr(node, 'orelse', False):
895+
return get_node_last_lineno(node.orelse[-1])
896+
# try statements have the 'handlers' last if there is no 'orelse' or 'finalbody'
897+
if getattr(node, 'handlers', False):
898+
return get_node_last_lineno(node.handlers[-1])
899+
# All compound statements have a 'body'
900+
if getattr(node, 'body', False):
901+
return get_node_last_lineno(node.body[-1])
902+
# Not a compound statement
903+
return node.lineno

pylint/test/unittest_checker_base.py

+24
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,30 @@ def func(tion):
5454
with self.assertNoMessages():
5555
self.checker.visit_functiondef(func)
5656

57+
@set_config(docstring_min_length=2)
58+
def test_long_function_no_docstring(self):
59+
func = astroid.extract_node("""
60+
def func(tion):
61+
pass
62+
pass
63+
""")
64+
message = Message('missing-docstring', node=func, args=('function',))
65+
with self.assertAddsMessages(message):
66+
self.checker.visit_functiondef(func)
67+
68+
@set_config(docstring_min_length=2)
69+
def test_long_function_nested_statements_no_docstring(self):
70+
func = astroid.extract_node("""
71+
def func(tion):
72+
try:
73+
pass
74+
except:
75+
pass
76+
""")
77+
message = Message('missing-docstring', node=func, args=('function',))
78+
with self.assertAddsMessages(message):
79+
self.checker.visit_functiondef(func)
80+
5781
@set_config(docstring_min_length=2)
5882
def test_function_no_docstring_by_name(self):
5983
func = astroid.extract_node("""

pylint/test/unittest_utils.py

+175-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import astroid
1515

1616
from pylint import utils
17-
from pylint.checkers.utils import check_messages
17+
from pylint.checkers.utils import check_messages, get_node_last_lineno
1818
from pylint.exceptions import InvalidMessageError
1919
import pytest
2020

@@ -207,3 +207,177 @@ def test_decoding_stream_known_encoding():
207207
binary_io = io.BytesIO(u'€'.encode('cp1252'))
208208
stream = utils.decoding_stream(binary_io, 'cp1252')
209209
assert stream.read() == u'€'
210+
211+
class TestGetNodeLastLineno:
212+
213+
def test_get_node_last_lineno_simple(self):
214+
node = astroid.extract_node("""
215+
pass
216+
""")
217+
assert get_node_last_lineno(node) == 2
218+
219+
220+
def test_get_node_last_lineno_if_simple(self):
221+
node = astroid.extract_node("""
222+
if True:
223+
print(1)
224+
pass
225+
""")
226+
assert get_node_last_lineno(node) == 4
227+
228+
229+
def test_get_node_last_lineno_if_elseif_else(self):
230+
node = astroid.extract_node("""
231+
if True:
232+
print(1)
233+
elif False:
234+
print(2)
235+
else:
236+
print(3)
237+
""")
238+
assert get_node_last_lineno(node) == 7
239+
240+
241+
def test_get_node_last_lineno_while(self):
242+
node = astroid.extract_node("""
243+
while True:
244+
print(1)
245+
""")
246+
assert get_node_last_lineno(node) == 3
247+
248+
249+
def test_get_node_last_lineno_while_else(self):
250+
node = astroid.extract_node("""
251+
while True:
252+
print(1)
253+
else:
254+
print(2)
255+
""")
256+
assert get_node_last_lineno(node) == 5
257+
258+
259+
def test_get_node_last_lineno_for(self):
260+
node = astroid.extract_node("""
261+
for x in range(0, 5):
262+
print(1)
263+
""")
264+
assert get_node_last_lineno(node) == 3
265+
266+
267+
def test_get_node_last_lineno_for_else(self):
268+
node = astroid.extract_node("""
269+
for x in range(0, 5):
270+
print(1)
271+
else:
272+
print(2)
273+
""")
274+
assert get_node_last_lineno(node) == 5
275+
276+
277+
def test_get_node_last_lineno_try(self):
278+
node = astroid.extract_node("""
279+
try:
280+
print(1)
281+
except ValueError:
282+
print(2)
283+
except Exception:
284+
print(3)
285+
""")
286+
assert get_node_last_lineno(node) == 7
287+
288+
289+
def test_get_node_last_lineno_try_except_else(self):
290+
node = astroid.extract_node("""
291+
try:
292+
print(1)
293+
except Exception:
294+
print(2)
295+
print(3)
296+
else:
297+
print(4)
298+
""")
299+
assert get_node_last_lineno(node) == 8
300+
301+
302+
def test_get_node_last_lineno_try_except_finally(self):
303+
node = astroid.extract_node("""
304+
try:
305+
print(1)
306+
except Exception:
307+
print(2)
308+
finally:
309+
print(4)
310+
""")
311+
assert get_node_last_lineno(node) == 7
312+
313+
314+
def test_get_node_last_lineno_try_except_else_finally(self):
315+
node = astroid.extract_node("""
316+
try:
317+
print(1)
318+
except Exception:
319+
print(2)
320+
else:
321+
print(3)
322+
finally:
323+
print(4)
324+
""")
325+
assert get_node_last_lineno(node) == 9
326+
327+
328+
def test_get_node_last_lineno_with(self):
329+
node = astroid.extract_node("""
330+
with x as y:
331+
print(1)
332+
pass
333+
""")
334+
assert get_node_last_lineno(node) == 4
335+
336+
337+
def test_get_node_last_lineno_method(self):
338+
node = astroid.extract_node("""
339+
def x(a, b):
340+
print(a, b)
341+
pass
342+
""")
343+
assert get_node_last_lineno(node) == 4
344+
345+
346+
def test_get_node_last_lineno_decorator(self):
347+
node = astroid.extract_node("""
348+
@decor()
349+
def x(a, b):
350+
print(a, b)
351+
pass
352+
""")
353+
assert get_node_last_lineno(node) == 5
354+
355+
def test_get_node_last_lineno_class(self):
356+
node = astroid.extract_node("""
357+
class C(object):
358+
CONST = True
359+
360+
def x(self, b):
361+
print(b)
362+
363+
def y(self):
364+
pass
365+
pass
366+
""")
367+
assert get_node_last_lineno(node) == 10
368+
369+
370+
def test_get_node_last_lineno_combined(self):
371+
node = astroid.extract_node("""
372+
class C(object):
373+
CONST = True
374+
375+
def y(self):
376+
try:
377+
pass
378+
except:
379+
pass
380+
finally:
381+
pass
382+
""")
383+
assert get_node_last_lineno(node) == 11

0 commit comments

Comments
 (0)