@@ -44,19 +44,19 @@ class PEP257Checker(object):
44
44
45
45
"""
46
46
47
- ALL_NUMPY_SECTIONS = ['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' ]
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
60
61
61
def check_source (self , source , filename , ignore_decorators ):
62
62
module = parse (StringIO (source ), filename )
@@ -444,35 +444,73 @@ def _rstrip_non_alpha(line):
444
444
if result is not None :
445
445
return result .group ()
446
446
447
+ @staticmethod
448
+ def _is_real_section (section , line , prev_line ):
449
+ """Check if the suspected line is a real section name or not.
450
+
451
+ This is done by checking the next conditions:
452
+ * Does the current line has a suffix after the suspected section name?
453
+ * Is the previous line not empty?
454
+ * Does the previous line end with a punctuation mark?
455
+
456
+ If so, this is probably not a real section name. For example:
457
+ '''Title.
458
+
459
+ Some part of the docstring that specifies what the function
460
+ returns. <----- Not a real section name.
461
+ '''
462
+ """
463
+ punctuation = [',' , ';' , '.' , '-' , '\\ ' , '/' , ']' , '}' , ')' ]
464
+ prev_line_ends_with_punctuation = \
465
+ any (prev_line .endswith (x ) for x in punctuation )
466
+ prev_line_is_empty = prev_line == ''
467
+
468
+ suffix = line .lstrip (section ).strip ()
469
+
470
+ # If there's a suffix to our suspected section name, and the previous
471
+ # line is not empty and ends with a punctuation mark, this is probably
472
+ # a false-positive.
473
+ return (suffix and not
474
+ prev_line_ends_with_punctuation and not
475
+ prev_line_is_empty )
476
+
447
477
@classmethod
448
478
def _check_section (cls , line_index , dashes_indices , lines ):
449
479
line = lines [line_index ].strip ()
480
+ prev_line = lines [line_index - 1 ].strip ()
450
481
section = cls ._rstrip_non_alpha (line )
451
- actual_section = section .title ()
482
+ capitalized_section = section .title ()
452
483
453
- if (section not in cls .ALL_NUMPY_SECTIONS and
454
- section .title () in cls .ALL_NUMPY_SECTIONS ):
455
- # The capitalized section string is not in the line,
456
- # meaning that the words appear there but not properly capitalized.
457
- yield violations .D405 (actual_section , section )
484
+ if cls ._is_real_section (section , line , prev_line ):
485
+ return
458
486
459
487
suffix = line .lstrip (section ).strip ()
460
488
if suffix :
461
- # The section name should end with a newline.
462
- yield violations .D406 (actual_section , suffix )
489
+ yield violations .D406 (capitalized_section , suffix )
490
+
491
+ if prev_line != '' :
492
+ yield violations .D410 (capitalized_section ) # Missing blank line
493
+
494
+ if (section not in cls .SECTION_NAMES and
495
+ capitalized_section in cls .SECTION_NAMES ):
496
+ yield violations .D405 (capitalized_section , section )
463
497
464
498
next_line_index = line_index + 1
465
499
if next_line_index not in dashes_indices :
466
- yield violations .D407 (actual_section )
500
+ yield violations .D407 (capitalized_section )
467
501
else :
468
- next_line_index += 1
469
502
if lines [next_line_index ].strip () != "-" * len (section ):
470
503
# The length of the underlining dashes does not
471
504
# match the length of the section name.
472
505
yield violations .D408 (section , len (section ))
473
506
507
+ # If there are no dashes - the next line after the section name
508
+ # should be empty. Otherwise, it's the next line after the dashes.
509
+ # This is why we increment the line index by 1 here.
510
+ next_line_index += 1
511
+
474
512
if lines [next_line_index ].strip ():
475
- yield violations .D409 (actual_section )
513
+ yield violations .D409 (capitalized_section )
476
514
477
515
@check_for (Definition )
478
516
def check_docstring_internal_structure (self , definition , docstring ):
@@ -485,7 +523,7 @@ def check_docstring_internal_structure(self, definition, docstring):
485
523
# It's not a multiple lined docstring
486
524
return
487
525
488
- lower_section_names = [s .lower () for s in self .ALL_NUMPY_SECTIONS ]
526
+ lower_section_names = [s .lower () for s in self .SECTION_NAMES ]
489
527
490
528
def _suspected_as_section (line ):
491
529
result = self ._rstrip_non_alpha (line .lower ())
@@ -502,121 +540,6 @@ def _contains_only_dashes(line):
502
540
for err in self ._check_section (i , dashes_indices , lines ):
503
541
yield err
504
542
505
- def SKIP_check_numpy (self , definition , docstring ):
506
- """Parse the general structure of a numpy docstring and check it."""
507
- if not docstring :
508
- return
509
-
510
- lines = docstring .split ("\n " )
511
- if len (lines ) < 2 :
512
- # It's not a multiple lined docstring
513
- return
514
-
515
- lines_generator = ScrollableIterator (lines [1 :]) # Skipping first line
516
- indent = self ._get_docstring_indent (definition , docstring )
517
-
518
- current_section = None
519
- curr_section_lines = []
520
- start_collecting_lines = False
521
-
522
- for line in lines_generator :
523
- for section in self .ALL_NUMPY_SECTIONS :
524
- with_colon = section .lower () + ':'
525
- if line .strip ().lower () in [section .lower (), with_colon ]:
526
- # There's a chance that this line is a numpy section
527
- try :
528
- next_line = lines_generator .next ()
529
- except StopIteration :
530
- # It probably isn't :)
531
- return
532
-
533
- if '' .join (set (next_line .strip ())) == '-' :
534
- # The next line contains only dashes, there's a good
535
- # chance that it's a numpy section
536
-
537
- if (leading_space (line ) > indent or
538
- leading_space (next_line ) > indent ):
539
- yield violations .D214 (section )
540
-
541
- if section not in line :
542
- # The capitalized section string is not in the line,
543
- # meaning that the word appears there but not
544
- # properly capitalized.
545
- yield violations .D405 (section , line .strip ())
546
- elif line .strip ().lower () == with_colon :
547
- # The section name should not end with a colon.
548
- yield violations .D406 (section , line .strip ())
549
-
550
- if next_line .strip () != "-" * len (section ):
551
- # The length of the underlining dashes does not
552
- # match the length of the section name.
553
- yield violations .D407 (section , len (section ))
554
-
555
- # At this point, we're done with the structured part of
556
- # the section and its underline.
557
- # We will now collect the content of each section and
558
- # let section handlers deal with it.
559
-
560
- if current_section is not None :
561
- for err in self ._check_numpy_section (
562
- current_section ,
563
- curr_section_lines ,
564
- definition ,
565
- docstring ):
566
- yield err
567
-
568
- start_collecting_lines = True
569
- current_section = section .lower ()
570
- curr_section_lines = []
571
- else :
572
- # The next line does not contain only dashes, so it's
573
- # not likely to be a section header.
574
- lines_generator .scroll_back ()
575
-
576
- if current_section is not None :
577
- if start_collecting_lines :
578
- start_collecting_lines = False
579
- else :
580
- curr_section_lines .append (line )
581
-
582
- if current_section is not None :
583
- for err in self ._check_numpy_section (current_section ,
584
- curr_section_lines ,
585
- definition ,
586
- docstring ):
587
- yield err
588
-
589
-
590
- class ScrollableIterator (object ):
591
- """An iterator over an iterable that can be moved back during iteration."""
592
-
593
- def __init__ (self , iterable ):
594
- self ._iterable = iterable
595
- self ._index = 0
596
-
597
- def __iter__ (self ):
598
- return self
599
-
600
- def next (self ):
601
- """Generate the next item or raise StopIteration."""
602
- try :
603
- return self ._iterable [self ._index ]
604
- except IndexError :
605
- raise StopIteration ()
606
- finally :
607
- self ._index += 1
608
-
609
- def scroll_back (self , num = 1 ):
610
- """Move the generator `num` items backwards."""
611
- if num < 0 :
612
- raise ValueError ('num cannot be a negative number' )
613
- self ._index = max (0 , self ._index - num )
614
-
615
- def clone (self ):
616
- """Return a copy of the generator set to the same item index."""
617
- obj_copy = self .__class__ (self ._iterable )
618
- obj_copy ._index = self ._index
619
-
620
543
621
544
parse = Parser ()
622
545
0 commit comments