Skip to content

Commit d89e226

Browse files
committed
TST: test source_file_def_line with decorators
1 parent 487fbbd commit d89e226

File tree

2 files changed

+59
-4
lines changed

2 files changed

+59
-4
lines changed

numpydoc/tests/test_validate.py

+55
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22
import warnings
3+
from inspect import getsourcelines
34

45
from numpydoc import validate
56
import numpydoc.tests
@@ -1528,6 +1529,35 @@ def test_bad_docstrings(self, capsys, klass, func, msgs):
15281529
assert msg in " ".join(err[1] for err in result["errors"])
15291530

15301531

1532+
def decorator(x):
1533+
"""Test decorator."""
1534+
return x
1535+
1536+
1537+
@decorator
1538+
@decorator
1539+
class DecoratorClass:
1540+
"""
1541+
Class and methods with decorators.
1542+
1543+
`DecoratorClass` has two decorators, `DecoratorClass.test_no_decorator` has no
1544+
decorator and `DecoratorClass.test_three_decorators` has three decorators.
1545+
`Validator.source_file_def_line` should return the `def` or `class` line number, not
1546+
the line of the first decorator.
1547+
"""
1548+
1549+
def test_no_decorator(self):
1550+
"""Test method without decorators."""
1551+
pass
1552+
1553+
@decorator
1554+
@decorator
1555+
@decorator
1556+
def test_three_decorators(self):
1557+
"""Test method with three decorators."""
1558+
pass
1559+
1560+
15311561
class TestValidatorClass:
15321562
@pytest.mark.parametrize("invalid_name", ["unknown_mod", "unknown_mod.MyClass"])
15331563
def test_raises_for_invalid_module_name(self, invalid_name):
@@ -1544,3 +1574,28 @@ def test_raises_for_invalid_attribute_name(self, invalid_name):
15441574
msg = f"'{obj_name}' has no attribute '{invalid_attr_name}'"
15451575
with pytest.raises(AttributeError, match=msg):
15461576
numpydoc.validate.Validator._load_obj(invalid_name)
1577+
1578+
@pytest.mark.parametrize(
1579+
["decorated_obj", "def_line"],
1580+
[
1581+
[
1582+
"numpydoc.tests.test_validate.DecoratorClass",
1583+
getsourcelines(DecoratorClass)[-1] + 2,
1584+
],
1585+
[
1586+
"numpydoc.tests.test_validate.DecoratorClass.test_no_decorator",
1587+
getsourcelines(DecoratorClass.test_no_decorator)[-1],
1588+
],
1589+
[
1590+
"numpydoc.tests.test_validate.DecoratorClass.test_three_decorators",
1591+
getsourcelines(DecoratorClass.test_three_decorators)[-1] + 3,
1592+
],
1593+
],
1594+
)
1595+
def test_source_file_def_line_with_decorators(self, decorated_obj, def_line):
1596+
doc = numpydoc.validate.Validator(
1597+
numpydoc.docscrape.get_doc_object(
1598+
numpydoc.validate.Validator._load_obj(decorated_obj)
1599+
)
1600+
)
1601+
assert doc.source_file_def_line == def_line

numpydoc/validate.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -298,14 +298,14 @@ def source_file_def_line(self):
298298
sourcelines = inspect.getsourcelines(self.code_obj)
299299
# getsourcelines will return the line of the first decorator found for the
300300
# current function. We have to find the def declaration after that.
301-
def_lines = [
301+
def_line = next(
302302
i
303303
for i, x in enumerate(
304-
[re.match("^ *(def|class) ", s) for s in sourcelines[0]]
304+
re.match("^ *(def|class) ", s) for s in sourcelines[0]
305305
)
306306
if x is not None
307-
]
308-
return sourcelines[-1] + def_lines[0]
307+
)
308+
return sourcelines[-1] + def_line
309309
except (OSError, TypeError):
310310
# In some cases the object is something complex like a cython
311311
# object that can't be easily introspected. An it's better to

0 commit comments

Comments
 (0)