Repository: numpy/numpydoc

Issue:
`numpydoc ignore` inline comment not recognized when using decorators
When using decorators, the `numpydoc ignore` inline comment does not seem to be recognized by either `numpydoc --validate` or the validation during Sphinx build. Here's a reprex, stored as `test.py`:

```python
import functools


def test_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper


@test_decorator
def test_function():  # numpydoc ignore=PR02
    """Test.

    Parameters
    ----------
    x : int
        Test.
    """
    print("test")
```

Using `python -m numpydoc --validate test.test_function` raises the warning `test.test_function:PR02:Unknown parameters {'x'}` when the decorator is used, but doesn't when the decorator is removed. Same thing when using validation during a Sphinx build. Interestingly, validation using `python -m numpydoc.hooks.validate_docstrings test.py` does not raise the PR02 warning, even when using the decorator.

Discussion:
larsoner: Thanks @sdiebolt !

Patch:
diff --git a/numpydoc/validate.py b/numpydoc/validate.py
index 2cd1125..e98d385 100644
--- a/numpydoc/validate.py
+++ b/numpydoc/validate.py
@@ -280,7 +280,13 @@ class Validator:
         File name where the object is implemented (e.g. pandas/core/frame.py).
         """
         try:
-            fname = inspect.getsourcefile(self.code_obj)
+            if isinstance(self.code_obj, property):
+                fname = inspect.getsourcefile(self.code_obj.fget)
+            elif isinstance(self.code_obj, functools.cached_property):
+                fname = inspect.getsourcefile(self.code_obj.func)
+            else:
+                fname = inspect.getsourcefile(self.code_obj)
+
         except TypeError:
             # In some cases the object is something complex like a cython
             # object that can't be easily introspected. An it's better to
@@ -295,7 +301,12 @@ class Validator:
         Number of line where the object is defined in its file.
         """
         try:
-            sourcelines = inspect.getsourcelines(self.code_obj)
+            if isinstance(self.code_obj, property):
+                sourcelines = inspect.getsourcelines(self.code_obj.fget)
+            elif isinstance(self.code_obj, functools.cached_property):
+                sourcelines = inspect.getsourcelines(self.code_obj.func)
+            else:
+                sourcelines = inspect.getsourcelines(self.code_obj)
             # getsourcelines will return the line of the first decorator found for the
             # current function. We have to find the def declaration after that.
             def_line = next(


Test patch:
diff --git a/numpydoc/tests/test_validate.py b/numpydoc/tests/test_validate.py
index 5f76cd9..68040ad 100644
--- a/numpydoc/tests/test_validate.py
+++ b/numpydoc/tests/test_validate.py
@@ -1,7 +1,8 @@
 import pytest
 import sys
 import warnings
-from inspect import getsourcelines
+from functools import cached_property
+from inspect import getsourcelines, getsourcefile
 
 from numpydoc import validate
 import numpydoc.tests
@@ -1541,8 +1542,12 @@ class DecoratorClass:
     """
     Class and methods with decorators.
 
-    `DecoratorClass` has two decorators, `DecoratorClass.test_no_decorator` has no
-    decorator and `DecoratorClass.test_three_decorators` has three decorators.
+    * `DecoratorClass` has two decorators.
+    * `DecoratorClass.test_no_decorator` has no decorator.
+    * `DecoratorClass.test_property` has a `@property` decorator.
+    * `DecoratorClass.test_cached_property` has a `@cached_property` decorator.
+    * `DecoratorClass.test_three_decorators` has three decorators.
+
     `Validator.source_file_def_line` should return the `def` or `class` line number, not
     the line of the first decorator.
     """
@@ -1551,6 +1556,16 @@ class DecoratorClass:
         """Test method without decorators."""
         pass
 
+    @property
+    def test_property(self):
+        """Test property method."""
+        pass
+
+    @cached_property
+    def test_cached_property(self):
+        """Test property method."""
+        pass
+
     @decorator
     @decorator
     @decorator
@@ -1577,7 +1592,7 @@ class TestValidatorClass:
             numpydoc.validate.Validator._load_obj(invalid_name)
 
     # inspect.getsourcelines does not return class decorators for Python 3.8. This was
-    # fixed starting with 3.9: https://github.com/python/cpython/issues/60060
+    # fixed starting with 3.9: https://github.com/python/cpython/issues/60060.
     @pytest.mark.parametrize(
         ["decorated_obj", "def_line"],
         [
@@ -1590,6 +1605,14 @@ class TestValidatorClass:
                 "numpydoc.tests.test_validate.DecoratorClass.test_no_decorator",
                 getsourcelines(DecoratorClass.test_no_decorator)[-1],
             ],
+            [
+                "numpydoc.tests.test_validate.DecoratorClass.test_property",
+                getsourcelines(DecoratorClass.test_property.fget)[-1] + 1,
+            ],
+            [
+                "numpydoc.tests.test_validate.DecoratorClass.test_cached_property",
+                getsourcelines(DecoratorClass.test_cached_property.func)[-1] + 1,
+            ],
             [
                 "numpydoc.tests.test_validate.DecoratorClass.test_three_decorators",
                 getsourcelines(DecoratorClass.test_three_decorators)[-1] + 3,
@@ -1603,3 +1626,24 @@ class TestValidatorClass:
             )
         )
         assert doc.source_file_def_line == def_line
+
+    @pytest.mark.parametrize(
+        ["property", "file_name"],
+        [
+            [
+                "numpydoc.tests.test_validate.DecoratorClass.test_property",
+                getsourcefile(DecoratorClass.test_property.fget),
+            ],
+            [
+                "numpydoc.tests.test_validate.DecoratorClass.test_cached_property",
+                getsourcefile(DecoratorClass.test_cached_property.func),
+            ],
+        ],
+    )
+    def test_source_file_name_with_properties(self, property, file_name):
+        doc = numpydoc.validate.Validator(
+            numpydoc.docscrape.get_doc_object(
+                numpydoc.validate.Validator._load_obj(property)
+            )
+        )
+        assert doc.source_file_name == file_name