6
6
import tokenize as tk
7
7
from itertools import takewhile
8
8
from re import compile as re
9
+ from collections import namedtuple
9
10
10
11
from . import violations
11
12
from .config import IllegalConfiguration
@@ -33,8 +34,8 @@ def decorator(f):
33
34
return decorator
34
35
35
36
36
- class PEP257Checker (object ):
37
- """Checker for PEP 257.
37
+ class ConventionChecker (object ):
38
+ """Checker for PEP 257 and numpy conventions .
38
39
39
40
D10x: Missing docstrings
40
41
D20x: Whitespace issues
@@ -43,6 +44,20 @@ class PEP257Checker(object):
43
44
44
45
"""
45
46
47
+ SECTION_NAMES = ['Short Summary' ,
48
+ 'Extended Summary' ,
49
+ 'Parameters' ,
50
+ 'Returns' ,
51
+ 'Yields' ,
52
+ 'Other Parameters' ,
53
+ 'Raises' ,
54
+ 'See Also' ,
55
+ 'Notes' ,
56
+ 'References' ,
57
+ 'Examples' ,
58
+ 'Attributes' ,
59
+ 'Methods' ]
60
+
46
61
def check_source (self , source , filename , ignore_decorators ):
47
62
module = parse (StringIO (source ), filename )
48
63
for definition in module :
@@ -54,7 +69,7 @@ def check_source(self, source, filename, ignore_decorators):
54
69
len (ignore_decorators .findall (dec .name )) > 0
55
70
for dec in definition .decorators )
56
71
if not skipping_all and not decorator_skip :
57
- error = this_check (None , definition ,
72
+ error = this_check (self , definition ,
58
73
definition .docstring )
59
74
else :
60
75
error = None
@@ -190,6 +205,13 @@ def check_blank_after_summary(self, definition, docstring):
190
205
if blanks_count != 1 :
191
206
return violations .D205 (blanks_count )
192
207
208
+ @staticmethod
209
+ def _get_docstring_indent (definition , docstring ):
210
+ """Return the indentation of the docstring's opening quotes."""
211
+ before_docstring , _ , _ = definition .source .partition (docstring )
212
+ _ , _ , indent = before_docstring .rpartition ('\n ' )
213
+ return indent
214
+
193
215
@check_for (Definition )
194
216
def check_indent (self , definition , docstring ):
195
217
"""D20{6,7,8}: The entire docstring should be indented same as code.
@@ -199,8 +221,7 @@ def check_indent(self, definition, docstring):
199
221
200
222
"""
201
223
if docstring :
202
- before_docstring , _ , _ = definition .source .partition (docstring )
203
- _ , _ , indent = before_docstring .rpartition ('\n ' )
224
+ indent = self ._get_docstring_indent (definition , docstring )
204
225
lines = docstring .split ('\n ' )
205
226
if len (lines ) > 1 :
206
227
lines = lines [1 :] # First line does not need indent.
@@ -390,6 +411,188 @@ def check_starts_with_this(self, function, docstring):
390
411
if first_word .lower () == 'this' :
391
412
return violations .D404 ()
392
413
414
+ @staticmethod
415
+ def _get_leading_words (line ):
416
+ """Return any leading set of words from `line`.
417
+
418
+ For example, if `line` is " Hello world!!!", returns "Hello world".
419
+ """
420
+ result = re ("[A-Za-z ]+" ).match (line .strip ())
421
+ if result is not None :
422
+ return result .group ()
423
+
424
+ @staticmethod
425
+ def _is_a_docstring_section (context ):
426
+ """Check if the suspected context is really a section header.
427
+
428
+ Lets have a look at the following example docstring:
429
+ '''Title.
430
+
431
+ Some part of the docstring that specifies what the function
432
+ returns. <----- Not a real section name. It has a suffix and the
433
+ previous line is not empty and does not end with
434
+ a punctuation sign.
435
+
436
+ This is another line in the docstring. It describes stuff,
437
+ but we forgot to add a blank line between it and the section name.
438
+ Returns <----- A real section name. The previous line ends with
439
+ ------- a period, therefore it is in a new
440
+ grammatical context.
441
+ Bla.
442
+
443
+ '''
444
+
445
+ To make sure this is really a section we check these conditions:
446
+ * There's no suffix to the section name.
447
+ * The previous line ends with punctuation.
448
+ * The previous line is empty.
449
+
450
+ If one of the conditions is true, we will consider the line as
451
+ a section name.
452
+ """
453
+ section_name_suffix = context .line .lstrip (context .section_name ).strip ()
454
+
455
+ punctuation = [',' , ';' , '.' , '-' , '\\ ' , '/' , ']' , '}' , ')' ]
456
+ prev_line_ends_with_punctuation = \
457
+ any (context .previous_line .strip ().endswith (x ) for x in punctuation )
458
+
459
+ return (is_blank (section_name_suffix ) or
460
+ prev_line_ends_with_punctuation or
461
+ is_blank (context .previous_line ))
462
+
463
+ @classmethod
464
+ def _check_section_underline (cls , section_name , context , indentation ):
465
+ """D4{07,08,09,10}, D215: Section underline checks.
466
+
467
+ Check for correct formatting for docstring sections. Checks that:
468
+ * The line that follows the section name contains
469
+ dashes (D40{7,8}).
470
+ * The amount of dashes is equal to the length of the section
471
+ name (D409).
472
+ * The line that follows the section header (with or without dashes)
473
+ is empty (D410).
474
+ * The indentation of the dashed line is equal to the docstring's
475
+ indentation (D215).
476
+ """
477
+ dash_line_found = False
478
+ next_non_empty_line_offset = 0
479
+
480
+ for line in context .following_lines :
481
+ line_set = '' .join (set (line .strip ()))
482
+ if not is_blank (line_set ):
483
+ dash_line_found = line_set == '-'
484
+ break
485
+ next_non_empty_line_offset += 1
486
+
487
+ if not dash_line_found :
488
+ yield violations .D407 (section_name )
489
+ if next_non_empty_line_offset == 0 :
490
+ yield violations .D410 (section_name )
491
+ else :
492
+ if next_non_empty_line_offset > 0 :
493
+ yield violations .D408 (section_name )
494
+
495
+ dash_line = context .following_lines [next_non_empty_line_offset ]
496
+ if dash_line .strip () != "-" * len (section_name ):
497
+ yield violations .D409 (len (section_name ),
498
+ section_name ,
499
+ len (dash_line .strip ()))
500
+
501
+ line_after_dashes = \
502
+ context .following_lines [next_non_empty_line_offset + 1 ]
503
+ if not is_blank (line_after_dashes ):
504
+ yield violations .D410 (section_name )
505
+
506
+ if leading_space (dash_line ) > indentation :
507
+ yield violations .D215 (section_name )
508
+
509
+ @classmethod
510
+ def _check_section (cls , docstring , definition , context ):
511
+ """D4{05,06,11}, D214: Section name checks.
512
+
513
+ Check for valid section names. Checks that:
514
+ * The section name is properly capitalized (D405).
515
+ * The section is not over-indented (D214).
516
+ * The section name has no superfluous suffix to it (D406).
517
+ * There's a blank line before the section (D411).
518
+
519
+ Also yields all the errors from `_check_section_underline`.
520
+ """
521
+ capitalized_section = context .section_name .title ()
522
+ indentation = cls ._get_docstring_indent (definition , docstring )
523
+
524
+ if (context .section_name not in cls .SECTION_NAMES and
525
+ capitalized_section in cls .SECTION_NAMES ):
526
+ yield violations .D405 (capitalized_section , context .section_name )
527
+
528
+ if leading_space (context .line ) > indentation :
529
+ yield violations .D214 (capitalized_section )
530
+
531
+ suffix = context .line .strip ().lstrip (context .section_name )
532
+ if suffix :
533
+ yield violations .D406 (capitalized_section , context .line .strip ())
534
+
535
+ if not is_blank (context .previous_line ):
536
+ yield violations .D411 (capitalized_section )
537
+
538
+ for err in cls ._check_section_underline (capitalized_section ,
539
+ context ,
540
+ indentation ):
541
+ yield err
542
+
543
+ @check_for (Definition )
544
+ def check_docstring_sections (self , definition , docstring ):
545
+ """D21{4,5}, D4{05,06,07,08,09,10}: Docstring sections checks.
546
+
547
+ Check the general format of a sectioned docstring:
548
+ '''This is my one-liner.
549
+
550
+ Short Summary
551
+ -------------
552
+
553
+ This is my summary.
554
+
555
+ Returns
556
+ -------
557
+
558
+ None.
559
+ '''
560
+
561
+ Section names appear in `SECTION_NAMES`.
562
+ """
563
+ if not docstring :
564
+ return
565
+
566
+ lines = docstring .split ("\n " )
567
+ if len (lines ) < 2 :
568
+ return
569
+
570
+ lower_section_names = [s .lower () for s in self .SECTION_NAMES ]
571
+
572
+ def _suspected_as_section (_line ):
573
+ result = self ._get_leading_words (_line .lower ())
574
+ return result in lower_section_names
575
+
576
+ # Finding our suspects.
577
+ suspected_section_indices = [i for i , line in enumerate (lines ) if
578
+ _suspected_as_section (line )]
579
+
580
+ SectionContext = namedtuple ('SectionContext' , ('section_name' ,
581
+ 'previous_line' ,
582
+ 'line' ,
583
+ 'following_lines' ))
584
+
585
+ contexts = (SectionContext (self ._get_leading_words (lines [i ].strip ()),
586
+ lines [i - 1 ],
587
+ lines [i ],
588
+ lines [i + 1 :])
589
+ for i in suspected_section_indices )
590
+
591
+ for ctx in contexts :
592
+ if self ._is_a_docstring_section (ctx ):
593
+ for err in self ._check_section (docstring , definition , ctx ):
594
+ yield err
595
+
393
596
394
597
parse = Parser ()
395
598
@@ -439,8 +642,8 @@ def check(filenames, select=None, ignore=None, ignore_decorators=None):
439
642
try :
440
643
with tokenize_open (filename ) as file :
441
644
source = file .read ()
442
- for error in PEP257Checker ().check_source (source , filename ,
443
- ignore_decorators ):
645
+ for error in ConventionChecker ().check_source (source , filename ,
646
+ ignore_decorators ):
444
647
code = getattr (error , 'code' , None )
445
648
if code in checked_codes :
446
649
yield error
0 commit comments