Repository: pandas-dev/pandas

Issue:
DOC: Validate reST directives in docstrings
#### Code Sample, a copy-pastable example if possible

```python
python scripts/validate_docstrings.py pandas.Series.item
```
#### Problem description
As pointed out by PR #27621, `scripts/validate_docstrings.py` does not detect dosctrings containing a reST directive without two colons.

#### Expected Output
The script should report an error in this case.

DOC: Validate reST directives in docstrings
#### Code Sample, a copy-pastable example if possible

```python
python scripts/validate_docstrings.py pandas.Series.item
```
#### Problem description
As pointed out by PR #27621, `scripts/validate_docstrings.py` does not detect dosctrings containing a reST directive without two colons.

#### Expected Output
The script should report an error in this case.



Discussion:

Resolving patch:
diff --git a/scripts/validate_docstrings.py b/scripts/validate_docstrings.py
--- 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:


Test patch:
diff --git a/scripts/tests/test_validate_docstrings.py b/scripts/tests/test_validate_docstrings.py
--- 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",