Skip to content

Commit b9ba708

Browse files
datapythonistaTomAugspurger
authored andcommitted
DOC: Adding validation of the section order in docstrings (#23607)
* Adding validation of the section order in docstrings * Updating allowed sections
1 parent 011b79f commit b9ba708

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

scripts/tests/test_validate_docstrings.py

+34
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,35 @@ def private_classes(self):
350350
This mentions NDFrame, which is not correct.
351351
"""
352352

353+
def unknown_section(self):
354+
"""
355+
This section has an unknown section title.
356+
357+
Unknown Section
358+
---------------
359+
This should raise an error in the validation.
360+
"""
361+
362+
def sections_in_wrong_order(self):
363+
"""
364+
This docstring has the sections in the wrong order.
365+
366+
Parameters
367+
----------
368+
name : str
369+
This section is in the right position.
370+
371+
Examples
372+
--------
373+
>>> print('So far Examples is good, as it goes before Parameters')
374+
So far Examples is good, as it goes before Parameters
375+
376+
See Also
377+
--------
378+
function : This should generate an error, as See Also needs to go
379+
before Examples.
380+
"""
381+
353382

354383
class BadSummaries(object):
355384

@@ -706,6 +735,11 @@ def test_bad_generic_functions(self, func):
706735
('BadGenericDocStrings', 'private_classes',
707736
("Private classes (NDFrame) should not be mentioned in public "
708737
'docstrings',)),
738+
('BadGenericDocStrings', 'unknown_section',
739+
('Found unknown section "Unknown Section".',)),
740+
('BadGenericDocStrings', 'sections_in_wrong_order',
741+
('Wrong order of sections. "See Also" should be located before '
742+
'"Notes"',)),
709743
('BadSeeAlso', 'desc_no_period',
710744
('Missing period at end of description for See Also "Series.iloc"',)),
711745
('BadSeeAlso', 'desc_first_letter_lowercase',

scripts/validate_docstrings.py

+38
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656

5757
PRIVATE_CLASSES = ['NDFrame', 'IndexOpsMixin']
5858
DIRECTIVES = ['versionadded', 'versionchanged', 'deprecated']
59+
ALLOWED_SECTIONS = ['Parameters', 'Attributes', 'Methods', 'Returns', 'Yields',
60+
'Other Parameters', 'Raises', 'Warns', 'See Also', 'Notes',
61+
'References', 'Examples']
5962
ERROR_MSGS = {
6063
'GL01': 'Docstring text (summary) should start in the line immediately '
6164
'after the opening quotes (not in the same line, or leaving a '
@@ -69,6 +72,10 @@
6972
'mentioned in public docstrings',
7073
'GL05': 'Tabs found at the start of line "{line_with_tabs}", please use '
7174
'whitespace only',
75+
'GL06': 'Found unknown section "{section}". Allowed sections are: '
76+
'{allowed_sections}',
77+
'GL07': 'Wrong order of sections. "{wrong_section}" should be located '
78+
'before "{goes_before}", the right order is: {sorted_sections}',
7279
'SS01': 'No summary found (a short summary in a single line should be '
7380
'present at the beginning of the docstring)',
7481
'SS02': 'Summary does not start with a capital letter',
@@ -353,6 +360,18 @@ def double_blank_lines(self):
353360
prev = row.strip()
354361
return False
355362

363+
@property
364+
def section_titles(self):
365+
sections = []
366+
self.doc._doc.reset()
367+
while not self.doc._doc.eof():
368+
content = self.doc._read_to_next_section()
369+
if (len(content) > 1
370+
and len(content[0]) == len(content[1])
371+
and set(content[1]) == {'-'}):
372+
sections.append(content[0])
373+
return sections
374+
356375
@property
357376
def summary(self):
358377
return ' '.join(self.doc['Summary'])
@@ -580,6 +599,25 @@ def validate_one(func_name):
580599
if re.match("^ *\t", line):
581600
errs.append(error('GL05', line_with_tabs=line.lstrip()))
582601

602+
unseen_sections = list(ALLOWED_SECTIONS)
603+
for section in doc.section_titles:
604+
if section not in ALLOWED_SECTIONS:
605+
errs.append(error('GL06',
606+
section=section,
607+
allowed_sections=', '.join(ALLOWED_SECTIONS)))
608+
else:
609+
if section in unseen_sections:
610+
section_idx = unseen_sections.index(section)
611+
unseen_sections = unseen_sections[section_idx + 1:]
612+
else:
613+
section_idx = ALLOWED_SECTIONS.index(section)
614+
goes_before = ALLOWED_SECTIONS[section_idx + 1]
615+
errs.append(error('GL07',
616+
sorted_sections=' > '.join(ALLOWED_SECTIONS),
617+
wrong_section=section,
618+
goes_before=goes_before))
619+
break
620+
583621
if not doc.summary:
584622
errs.append(error('SS01'))
585623
else:

0 commit comments

Comments
 (0)