diff --git a/ci/code_checks.sh b/ci/code_checks.sh index 06d45e38bfcdb..333136ddfddd9 100755 --- a/ci/code_checks.sh +++ b/ci/code_checks.sh @@ -263,8 +263,8 @@ fi ### DOCSTRINGS ### if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then - MSG='Validate docstrings (GL03, GL04, GL05, GL06, GL07, GL09, SS04, SS05, PR03, PR04, PR05, PR10, EX04, RT01, RT04, RT05, SA05)' ; echo $MSG - $BASE_DIR/scripts/validate_docstrings.py --format=azure --errors=GL03,GL04,GL05,GL06,GL07,GL09,SS04,SS05,PR03,PR04,PR05,PR10,EX04,RT01,RT04,RT05,SA05 + MSG='Validate docstrings (GL03, GL04, GL05, GL06, GL07, GL09, GL10, SS04, SS05, PR03, PR04, PR05, PR10, EX04, RT01, RT04, RT05, SA05)' ; echo $MSG + $BASE_DIR/scripts/validate_docstrings.py --format=azure --errors=GL03,GL04,GL05,GL06,GL07,GL09,GL10,SS04,SS05,PR03,PR04,PR05,PR10,EX04,RT01,RT04,RT05,SA05 RET=$(($RET + $?)) ; echo $MSG "DONE" fi diff --git a/scripts/tests/test_validate_docstrings.py b/scripts/tests/test_validate_docstrings.py index f3364e6725a20..35aaf10458f44 100644 --- a/scripts/tests/test_validate_docstrings.py +++ b/scripts/tests/test_validate_docstrings.py @@ -200,7 +200,7 @@ def contains(self, pat, case=True, na=np.nan): def mode(self, axis, numeric_only): """ - Ensure sphinx directives don't affect checks for trailing periods. + Ensure reST directives don't affect checks for leading periods. Parameters ---------- @@ -447,6 +447,27 @@ def deprecation_in_wrong_order(self): def method_wo_docstrings(self): pass + def directives_without_two_colons(self, first, second): + """ + Ensure reST directives have trailing colons. + + Parameters + ---------- + first : str + Sentence ending in period, followed by single directive w/o colons. + + .. versionchanged 0.1.2 + + second : bool + Sentence ending in period, followed by multiple directives w/o + colons. + + .. versionadded 0.1.2 + .. deprecated 0.00.0 + + """ + pass + class BadSummaries: def wrong_line(self): @@ -840,6 +861,7 @@ def test_bad_class(self, capsys): "plot", "method", "private_classes", + "directives_without_two_colons", ], ) def test_bad_generic_functions(self, capsys, func): @@ -879,6 +901,14 @@ def test_bad_generic_functions(self, capsys, func): "deprecation_in_wrong_order", ("Deprecation warning should precede extended summary",), ), + ( + "BadGenericDocStrings", + "directives_without_two_colons", + ( + "reST directives ['versionchanged', 'versionadded', " + "'deprecated'] must be followed by two colons", + ), + ), ( "BadSeeAlso", "desc_no_period", diff --git a/scripts/validate_docstrings.py b/scripts/validate_docstrings.py index 37623d32db685..bf5d861281a36 100755 --- a/scripts/validate_docstrings.py +++ b/scripts/validate_docstrings.py @@ -59,6 +59,7 @@ PRIVATE_CLASSES = ["NDFrame", "IndexOpsMixin"] DIRECTIVES = ["versionadded", "versionchanged", "deprecated"] +DIRECTIVE_PATTERN = re.compile(rf"^\s*\.\. ({'|'.join(DIRECTIVES)})(?!::)", re.I | re.M) ALLOWED_SECTIONS = [ "Parameters", "Attributes", @@ -93,6 +94,7 @@ "GL07": "Sections are in the wrong order. Correct order is: " "{correct_sections}", "GL08": "The object does not have a docstring", "GL09": "Deprecation warning should precede extended summary", + "GL10": "reST directives {directives} must be followed by two colons", "SS01": "No summary found (a short summary in a single line should be " "present at the beginning of the docstring)", "SS02": "Summary does not start with a capital letter", @@ -478,6 +480,10 @@ def parameter_mismatches(self): def correct_parameters(self): return not bool(self.parameter_mismatches) + @property + def directives_without_two_colons(self): + return DIRECTIVE_PATTERN.findall(self.raw_doc) + def parameter_type(self, param): return self.doc_parameters[param][0] @@ -697,6 +703,10 @@ def get_validation_data(doc): if doc.deprecated and not doc.extended_summary.startswith(".. deprecated:: "): errs.append(error("GL09")) + directives_without_two_colons = doc.directives_without_two_colons + if directives_without_two_colons: + errs.append(error("GL10", directives=directives_without_two_colons)) + if not doc.summary: errs.append(error("SS01")) else: