Skip to content

Commit 88571c6

Browse files
barometzjsh9
andauthored
Support @property + @abstractmethod without a Returns: section (#261)
Co-authored-by: jsh9 <[email protected]>
1 parent f08dd85 commit 88571c6

File tree

8 files changed

+75
-13
lines changed

8 files changed

+75
-13
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Change Log
22

3+
## [0.7.5] - 2025-10-25
4+
5+
- Fixed
6+
- Fixed the logic to statically detect whether a method is a property or an
7+
abstract method
8+
- Full diff
9+
- https://github.com/jsh9/pydoclint/compare/0.7.4...0.7.5
10+
311
## [0.7.4] - 2025-10-24
412

513
- Added

docs/violation_codes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ Other potential causes to `DOC103` include:
5959
| `DOC202` | Function/method has a return section in docstring, but there are no return statements or annotations |
6060
| `DOC203` | Return type(s) in the docstring not consistent with the return annotation |
6161

62-
Note on `DOC201`: Methods with `@property` as its last decorator do not need to
63-
have a return section.
62+
Note on `DOC201`: Methods with `@property` as its outer-most decorator (i.e.,
63+
on the top) do not need to have a return section.
6464

6565
## 3. `DOC3xx`: Violations about class docstring and class constructor
6666

pydoclint/utils/special_methods.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@ def checkMethodContainsSpecifiedDecorator(
2121
return (
2222
isinstance(node.decorator_list, list)
2323
and len(node.decorator_list) > 0
24-
and any(
25-
( # noqa: PAR001
26-
isinstance(_, ast.Name)
27-
and hasattr(node.decorator_list[-1], 'id')
28-
and _.id == decorator
29-
)
30-
for _ in node.decorator_list
31-
)
24+
# Only the outermost (0th element) decorator counts
25+
and ast.unparse(node.decorator_list[0]) == decorator
3226
)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "pydoclint"
7-
version = "0.7.4"
7+
version = "0.7.5"
88
description = "A Python docstring linter that checks arguments, returns, yields, and raises sections"
99
readme = "README.md"
1010
license = {text = "MIT"}

tests/test_data/common/property_method.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,43 @@ def something(self) -> float:
1010
is a "property method" and is intended to be used as an attribute.
1111
"""
1212
return self.data
13+
14+
class MyOtherClass:
15+
def __init__(self):
16+
pass
17+
18+
@property
19+
@something
20+
@something_else
21+
@well
22+
def method_1(self) -> float:
23+
"""
24+
Some abstract property.
25+
26+
This is also OK. When @property is the outer-most decorator (i.e., at
27+
the top), the method is still considered a "property method" and thus
28+
does not need a return section.
29+
"""
30+
pass
31+
32+
@something
33+
@property
34+
@something_else
35+
@well
36+
def method_2(self) -> float:
37+
"""
38+
This is NOT a property method, because @property is not the outer-most
39+
decorator. Therefore, this method should have a return section.
40+
"""
41+
pass
42+
43+
@something
44+
@something_else
45+
@well
46+
@property
47+
def method_3(self) -> float:
48+
"""
49+
This is NOT a property method, because @property is not the outer-most
50+
decorator. Therefore, this method should have a return section.
51+
"""
52+
pass

tests/test_data/google/abstract_method/cases.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ class AbstractClass(ABC):
66
"""Example abstract class."""
77

88
@abstractmethod
9+
@something
10+
@something_else
911
def abstract_method(self, var1: str) -> Generator[str, None, None]:
1012
"""Abstract method.
1113
@@ -22,6 +24,8 @@ def abstract_method(self, var1: str) -> Generator[str, None, None]:
2224
"""
2325

2426
@abstractmethod
27+
@hello
28+
@world
2529
def another_abstract_method(self, var1: str) -> Iterator[str]:
2630
"""Another abstract method.
2731
@@ -41,6 +45,8 @@ def another_abstract_method(self, var1: str) -> Iterator[str]:
4145
"""
4246

4347
@abstractmethod
48+
@good
49+
@morning
4450
def third_abstract_method(self, var1: str) -> str:
4551
"""The 3rd abstract method.
4652

tests/test_main.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1241,7 +1241,19 @@ def testNoReturnSectionInPropertyMethod(style: str) -> None:
12411241
skipCheckingShortDocstrings=False,
12421242
checkClassAttributes=False,
12431243
)
1244-
assert len(violations) == 0
1244+
expected = [
1245+
'DOC201: Method `MyOtherClass.method_2` does not have a return section in '
1246+
'docstring',
1247+
'DOC203: Method `MyOtherClass.method_2` return type(s) in docstring not '
1248+
'consistent with the return annotation. Return annotation has 1 type(s); '
1249+
'docstring return section has 0 type(s).',
1250+
'DOC201: Method `MyOtherClass.method_3` does not have a return section in '
1251+
'docstring',
1252+
'DOC203: Method `MyOtherClass.method_3` return type(s) in docstring not '
1253+
'consistent with the return annotation. Return annotation has 1 type(s); '
1254+
'docstring return section has 0 type(s).',
1255+
]
1256+
assert list(map(str, violations)) == expected
12451257

12461258

12471259
@pytest.mark.parametrize(

tests/utils/test_special_methods.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def method1(self):
3939
4040
class A:
4141
@hello_world
42+
@morning
4243
def method1(self):
4344
pass
4445
"""
@@ -49,8 +50,9 @@ def method1(self):
4950
[
5051
(src1, 'something', False),
5152
(src2, 'property', True),
52-
(src3, 'property', True),
53+
(src3, 'property', False), # because `@property` is not outermost
5354
(src4, 'hello_world', True),
55+
(src4, 'morning', False), # because `@morning` isn't outermost
5456
(src4, 'property', False),
5557
],
5658
)

0 commit comments

Comments
 (0)