Repository: pandas-dev/pandas

Issue:
DOC: Detect whether a Returns section is needed in validate_docstrings.py
One of the validations performed in the docstrings in `validate_docstrings.py` is whether a `Returns` section is present. So far we just check if the `return` word exist in the source code, and if it does, we generate an error if the docsting does not have the documentation section.

But this causes false positives, see this case:
```python
def say_hello(lang):
    """
    This function does not have a Returns section.

    This is correct because nothing is returned.

    Parameters
    ----------
    lang : str
         Language to say hello in.
     """
     if lang not in ('en', 'es'):
         return

    print({'en': 'Hi!', 'es': 'Hola!'}[lang])
```

The `return` keyword is found in the code, so the presence of a `Returns` section is validated, but it should not.

Without making anything complicated, it'd be nice to check when validating:
- That the return words found are not a bare return or a `return None` or are not comments...

If any code `return` is found that does not return always `None` is found, then we should show the error. 


Discussion:
Moving our conversation here from the referenced issue. Understood now what you are saying.

Unless we have some kind of code that performs a conditional mutation on an argument would this ever really show up? If not just not sure it's worth the extra complexity
I had hoped that the `ast` module would work out of the box, but not quite.


```python
In [44]: tree = ast.parse(inspect.getsource(pd.DataFrame.head).strip())

In [45]: tree.body[0].returns

```

https://stackoverflow.com/a/48366253/1889400 has some suggestions.

So mostly in agreement with @WillAyd, maybe not worth the trouble.
I think with a regex should be quite straight forward.

For the short term I don't think it's important. But as we manage to add the `validation_docstrings.py` in the CI, if we can't identify if the function can return something other than None, we won't be able to validate docstrings with missing Returns section.

But we can come back to it once we're there. So much to do before. :)
Hi.

This will be my first attempt at contributing to open source. Can someone help me out?

So far I have written a regex that I believe solves the problem at hand. I tested it in a separated file, against a few cases I could think of, since I can't really figure out how to use test_validation_docstrings.py. Is there an automated test I can run, and how?

I have also written a method to the GoodDocStrings class that has:

- an empty return
- a "return None"
- a commentary with the word "return"

but has no Return section on the docstring.

Can I place those codes here? Or should I open a pull request even without having run the test_validation_docstrings.py myself? Please, I need some orientation.



What's the problem with `test_validation_docstrings.py`? Can't you run the tests? In a pandas environment, you should simply run in the root `python -m pytest scripts/`.

If that was the problem, feel free to open a separate PR to clarify that in the documentation for pandas developers.

When you're happy with your changes, open a PR, posting the code in a comment is not really useful.

Resolving patch:
diff --git a/scripts/validate_docstrings.py b/scripts/validate_docstrings.py
--- a/scripts/validate_docstrings.py
+++ b/scripts/validate_docstrings.py
@@ -26,6 +26,8 @@
 import importlib
 import doctest
 import tempfile
+import ast
+import textwrap
 
 import flake8.main.application
 
@@ -490,9 +492,45 @@ def yields(self):
     @property
     def method_source(self):
         try:
-            return inspect.getsource(self.obj)
+            source = inspect.getsource(self.obj)
         except TypeError:
             return ''
+        return textwrap.dedent(source)
+
+    @property
+    def method_returns_something(self):
+        '''
+        Check if the docstrings method can return something.
+
+        Bare returns, returns valued None and returns from nested functions are
+        disconsidered.
+
+        Returns
+        -------
+        bool
+            Whether the docstrings method can return something.
+        '''
+
+        def get_returns_not_on_nested_functions(node):
+            returns = [node] if isinstance(node, ast.Return) else []
+            for child in ast.iter_child_nodes(node):
+                # Ignore nested functions and its subtrees.
+                if not isinstance(child, ast.FunctionDef):
+                    child_returns = get_returns_not_on_nested_functions(child)
+                    returns.extend(child_returns)
+            return returns
+
+        tree = ast.parse(self.method_source).body
+        if tree:
+            returns = get_returns_not_on_nested_functions(tree[0])
+            return_values = [r.value for r in returns]
+            # Replace NameConstant nodes valued None for None.
+            for i, v in enumerate(return_values):
+                if isinstance(v, ast.NameConstant) and v.value is None:
+                    return_values[i] = None
+            return any(return_values)
+        else:
+            return False
 
     @property
     def first_line_ends_in_dot(self):
@@ -691,7 +729,7 @@ def get_validation_data(doc):
 
     if doc.is_function_or_method:
         if not doc.returns:
-            if 'return' in doc.method_source:
+            if doc.method_returns_something:
                 errs.append(error('RT01'))
         else:
             if len(doc.returns) == 1 and doc.returns[0][1]:


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
@@ -229,6 +229,27 @@ def good_imports(self):
         """
         pass
 
+    def no_returns(self):
+        """
+        Say hello and have no returns.
+        """
+        pass
+
+    def empty_returns(self):
+        """
+        Say hello and always return None.
+
+        Since this function never returns a value, this
+        docstring doesn't need a return section.
+        """
+        def say_hello():
+            return "Hello World!"
+        say_hello()
+        if True:
+            return
+        else:
+            return None
+
 
 class BadGenericDocStrings(object):
     """Everything here has a bad docstring
@@ -783,7 +804,7 @@ def test_good_class(self, capsys):
 
     @pytest.mark.parametrize("func", [
         'plot', 'sample', 'random_letters', 'sample_values', 'head', 'head1',
-        'contains', 'mode', 'good_imports'])
+        'contains', 'mode', 'good_imports', 'no_returns', 'empty_returns'])
     def test_good_functions(self, capsys, func):
         errors = validate_one(self._import_path(
             klass='GoodDocStrings', func=func))['errors']