Skip to content

Commit 4664a5b

Browse files
authored
Duplicates (#10)
* add documentation and tests for duplicate args * implement duplicate check for args * add checks for duplicate attrs * add check that exceptions are only described once * increment version and add release
1 parent 86dd4e7 commit 4664a5b

File tree

10 files changed

+573
-12
lines changed

10 files changed

+573
-12
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## [Unreleased]
44

5+
## [v1.1.0] - 2023-01-26
6+
7+
### Added
8+
9+
- Lint check that ensures all function/ method arguments are described only
10+
once.
11+
- Lint check that ensures all class attributes are described only once.
12+
- Lint check that ensures all raised exceptions are described only once.
13+
514
## [v1.0.4] - 2023-01-13
615

716
### Changed
@@ -93,3 +102,4 @@
93102
[v1.0.2]: https://github.com/jdkandersson/flake8-docstrings-complete/releases/v1.0.2
94103
[v1.0.3]: https://github.com/jdkandersson/flake8-docstrings-complete/releases/v1.0.3
95104
[v1.0.4]: https://github.com/jdkandersson/flake8-docstrings-complete/releases/v1.0.4
105+
[v1.1.0]: https://github.com/jdkandersson/flake8-docstrings-complete/releases/v1.1.0

README.md

Lines changed: 164 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,21 @@ def foo(bar, baz):
5757
in the argument section.
5858
3. If an arguments section is in the function/ method docstring, the argument
5959
section contains no arguments the function/ method doesn't have.
60-
4. If a function/ method has a return statement with a value, the return value
60+
4. Function/ method arguments are only documented once.
61+
5. If a function/ method has a return statement with a value, the return value
6162
section is included.
62-
5. If a function/ method has a yield statement with a value, the yield value
63+
6. If a function/ method has a yield statement with a value, the yield value
6364
section is included.
64-
6. If a function/ method raises an exception, the raises section is included
65+
7. If a function/ method raises an exception, the raises section is included
6566
with a description for each exception that is raised.
66-
7. If a class has public attributes, that the attributes section is included.
67-
8. If a class has public attributes, that all public attributes are in the
67+
8. Each raised exception is only described once.
68+
9. If a class has public attributes, that the attributes section is included.
69+
10. If a class has public attributes, that all public attributes are in the
6870
attributes section.
69-
9. If an attributes section is in the class docstring, the attributes section
71+
11. If an attributes section is in the class docstring, the attributes section
7072
contains no attributes the class doesn't have.
71-
10. Any of the sections being checked are not present multiple times.
73+
12. Class attributes are only documented once.
74+
13. Any of the sections being checked are not present multiple times.
7275

7376
Note:
7477

@@ -107,6 +110,8 @@ A few rules have been defined to allow for selective suppression:
107110
docstring.
108111
- `DCO024`: function/ method has one or more arguments described in the
109112
docstring which are not arguments of the function/ method.
113+
- `DCO025`: function/ method has one or more arguments described in the
114+
docstring multiple times.
110115
- `DCO030`: function/ method that returns a value does not have the returns
111116
section in the docstring.
112117
- `DCO031`: function/ method that does not return a value has the returns
@@ -131,6 +136,8 @@ A few rules have been defined to allow for selective suppression:
131136
docstring which are not raised in the function/ method.
132137
- `DCO055`: function/ method that has a raise without an exception has an empty
133138
raises section in the docstring.
139+
- `DCO056`: function/ method has one or more exceptions described in the
140+
docstring multiple times.
134141
- `DCO060`: class has one or more public attributes and the docstring does not
135142
have an attributes section.
136143
- `DCO061`: class with no attributes and the docstring has an attributes
@@ -141,6 +148,8 @@ A few rules have been defined to allow for selective suppression:
141148
docstring.
142149
- `DCO064`: class has one or more attributes described in the docstring which
143150
are not attributes of the class.
151+
- `DCO065`: class has one or more attributes described in the docstring
152+
multiple times.
144153

145154
### Fix DCO010
146155

@@ -446,6 +455,50 @@ class FooClass:
446455
"""
447456
```
448457

458+
### Fix DCO025
459+
460+
This linting rule is triggered by a function/ method that has one or more
461+
arguments and a docstring that describes one or more arguments where on or more
462+
of the described arguments are described multiple times. For example:
463+
464+
```Python
465+
def foo(bar):
466+
"""Perform foo action.
467+
468+
Args:
469+
bar: the value to perform the foo action on.
470+
bar: the value to perform the foo action on.
471+
"""
472+
473+
class FooClass:
474+
def foo(self, bar):
475+
"""Perform foo action.
476+
477+
Args:
478+
bar: the value to perform the foo action on.
479+
bar: the value to perform the foo action on.
480+
"""
481+
```
482+
483+
These examples can be fixed by removing the duplicate arguments from the docstring:
484+
485+
```Python
486+
def foo(bar):
487+
"""Perform foo action.
488+
489+
Args:
490+
bar: the value to perform the foo action on.
491+
"""
492+
493+
class FooClass:
494+
def foo(self, bar):
495+
"""Perform foo action.
496+
497+
Args:
498+
bar: the value to perform the foo action on.
499+
"""
500+
```
501+
449502
### Fix DCO030
450503

451504
This linting rule is triggered by a function/ method that has at least one
@@ -1121,6 +1174,55 @@ class FooClass:
11211174
raise
11221175
```
11231176

1177+
### Fix DCO056
1178+
1179+
This linting rule is triggered by a function/ method that raises one or more
1180+
exceptions and a docstring that describes one or more exceptions where on or
1181+
more of the described exceptions are described multiple times. For example:
1182+
1183+
```Python
1184+
def foo():
1185+
"""Perform foo action.
1186+
1187+
Raises:
1188+
BarError: the value to perform the foo action on was wrong.
1189+
BarError: the value to perform the foo action on was wrong.
1190+
"""
1191+
raise BarError
1192+
1193+
class FooClass:
1194+
def foo(self):
1195+
"""Perform foo action.
1196+
1197+
Raises:
1198+
BarError: the value to perform the foo action on was wrong.
1199+
BarError: the value to perform the foo action on was wrong.
1200+
"""
1201+
raise BarError
1202+
```
1203+
1204+
These examples can be fixed by removing the duplicate descriptions from the
1205+
docstring:
1206+
1207+
```Python
1208+
def foo():
1209+
"""Perform foo action.
1210+
1211+
Raises:
1212+
BarError: the value to perform the foo action on was wrong.
1213+
"""
1214+
raise BarError
1215+
1216+
class FooClass:
1217+
def foo(self):
1218+
"""Perform foo action.
1219+
1220+
Raises:
1221+
BarError: the value to perform the foo action on was wrong.
1222+
"""
1223+
raise BarError
1224+
```
1225+
11241226
### Fix DCO060
11251227

11261228
This linting rule is triggered by a class that has one or more public
@@ -1431,6 +1533,61 @@ class FooClass:
14311533
self.bar = "bar"
14321534
```
14331535

1536+
### Fix DCO065
1537+
1538+
This linting rule is triggered by a class that has one or more attributes and a
1539+
docstring that describes one or more attributes where on or more
1540+
of the described attributes are described multiple times. For example:
1541+
1542+
```Python
1543+
class FooClass:
1544+
"""Perform foo action.
1545+
1546+
Attrs:
1547+
bar: The value to perform the foo action on.
1548+
bar: The value to perform the foo action on.
1549+
"""
1550+
1551+
bar = "bar"
1552+
1553+
class FooClass:
1554+
"""Perform foo action.
1555+
1556+
Attrs:
1557+
bar: The value to perform the foo action on.
1558+
bar: The value to perform the foo action on.
1559+
"""
1560+
1561+
def __init__(self):
1562+
"""Construct."""
1563+
self.bar = "bar"
1564+
```
1565+
1566+
These examples can be fixed by removing the duplicate descriptions from the
1567+
docstring:
1568+
1569+
```Python
1570+
class FooClass:
1571+
"""Perform foo action.
1572+
1573+
Attrs:
1574+
bar: The value to perform the foo action on.
1575+
"""
1576+
1577+
bar = "bar"
1578+
1579+
class FooClass:
1580+
"""Perform foo action.
1581+
1582+
Attrs:
1583+
bar: The value to perform the foo action on.
1584+
"""
1585+
1586+
def __init__(self):
1587+
"""Construct."""
1588+
self.bar = "bar"
1589+
```
1590+
14341591
## Docstring Examples
14351592

14361593
Examples of function/ method and class docstrings are:

flake8_docstrings_complete/args.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import ast
6+
from collections import Counter
67
from typing import Iterator
78

89
from . import docstring, types_
@@ -35,6 +36,11 @@
3536
f'{ARG_IN_DOCSTR_CODE} "%s" argument should not be described in the docstring{MORE_INFO_BASE}'
3637
f"{ARG_IN_DOCSTR_CODE.lower()}"
3738
)
39+
DUPLICATE_ARG_CODE = f"{ERROR_CODE_PREFIX}025"
40+
DUPLICATE_ARG_MSG = (
41+
f'{DUPLICATE_ARG_CODE} "%s" argument documented multiple times{MORE_INFO_BASE}'
42+
f"{DUPLICATE_ARG_CODE.lower()}"
43+
)
3844

3945
SKIP_ARGS = {"self", "cls"}
4046
UNUSED_ARGS_PREFIX = "_"
@@ -91,7 +97,9 @@ def check(
9197
yield types_.Problem(
9298
docstr_node.lineno, docstr_node.col_offset, ARGS_SECTION_IN_DOCSTR_MSG
9399
)
94-
elif all_args and docstr_info.args is not None:
100+
101+
# Checks for function with arguments and args section
102+
if all_args and docstr_info.args is not None:
95103
docstr_args = set(docstr_info.args)
96104

97105
# Check for multiple args sections
@@ -116,6 +124,14 @@ def check(
116124
for arg in sorted(docstr_args - func_args)
117125
)
118126

127+
# Check for duplicate arguments
128+
arg_occurrences = Counter(docstr_info.args)
129+
yield from (
130+
types_.Problem(docstr_node.lineno, docstr_node.col_offset, DUPLICATE_ARG_MSG % arg)
131+
for arg, occurrences in arg_occurrences.items()
132+
if occurrences > 1
133+
)
134+
119135
# Check for empty args section
120136
if not all_used_args and len(docstr_info.args) == 0:
121137
yield types_.Problem(

flake8_docstrings_complete/attrs.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import ast
6+
from collections import Counter
67
from itertools import chain
78
from typing import Iterable, Iterator
89

@@ -34,6 +35,11 @@
3435
f'{ATTR_IN_DOCSTR_CODE} "%s" attribute should not be described in the docstring'
3536
f"{MORE_INFO_BASE}{ATTR_IN_DOCSTR_CODE.lower()}"
3637
)
38+
DUPLICATE_ATTR_CODE = f"{ERROR_CODE_PREFIX}065"
39+
DUPLICATE_ATTR_MSG = (
40+
f'{DUPLICATE_ATTR_CODE} "%s" attribute documented multiple times{MORE_INFO_BASE}'
41+
f"{DUPLICATE_ATTR_CODE.lower()}"
42+
)
3743

3844
CLASS_SELF_CLS = {"self", "cls"}
3945
PRIVATE_ATTR_PREFIX = "_"
@@ -186,12 +192,15 @@ def check(
186192
yield types_.Problem(
187193
docstr_node.lineno, docstr_node.col_offset, ATTRS_SECTION_NOT_IN_DOCSTR_MSG
188194
)
195+
189196
# Check that attrs section is not in docstring if class has no attributes
190197
if not all_targets and docstr_info.attrs is not None:
191198
yield types_.Problem(
192199
docstr_node.lineno, docstr_node.col_offset, ATTRS_SECTION_IN_DOCSTR_MSG
193200
)
194-
elif all_targets and docstr_info.attrs is not None:
201+
202+
# Checks for class with attributes and an attrs section
203+
if all_targets and docstr_info.attrs is not None:
195204
docstr_attrs = set(docstr_info.attrs)
196205

197206
# Check for multiple attrs sections
@@ -216,6 +225,14 @@ def check(
216225
for attr in sorted(docstr_attrs - class_attrs)
217226
)
218227

228+
# Check for duplicate attributes
229+
attr_occurrences = Counter(docstr_info.attrs)
230+
yield from (
231+
types_.Problem(docstr_node.lineno, docstr_node.col_offset, DUPLICATE_ATTR_MSG % attr)
232+
for attr, occurrences in attr_occurrences.items()
233+
if occurrences > 1
234+
)
235+
219236
# Check for empty attrs section
220237
if not all_public_class_targets and len(docstr_info.attrs) == 0:
221238
yield types_.Problem(

flake8_docstrings_complete/raises.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import ast
6+
from collections import Counter
67
from typing import Iterable, Iterator
78

89
from . import docstring, types_
@@ -42,6 +43,11 @@
4243
"describe at least one exception in the raises section of the docstring"
4344
f"{MORE_INFO_BASE}{RE_RAISE_NO_EXC_IN_DOCSTR_CODE.lower()}"
4445
)
46+
DUPLICATE_EXC_CODE = f"{ERROR_CODE_PREFIX}056"
47+
DUPLICATE_EXC_MSG = (
48+
f'{DUPLICATE_EXC_CODE} "%s" exception documented multiple times{MORE_INFO_BASE}'
49+
f"{DUPLICATE_EXC_CODE.lower()}"
50+
)
4551

4652

4753
def _get_exc_node(node: ast.Raise) -> types_.Node | None:
@@ -116,7 +122,9 @@ def check(
116122
yield types_.Problem(
117123
docstr_node.lineno, docstr_node.col_offset, RE_RAISE_NO_EXC_IN_DOCSTR_MSG
118124
)
119-
elif all_excs and docstr_info.raises is not None:
125+
126+
# Checks for exceptions raised and raises section in docstring
127+
if all_excs and docstr_info.raises is not None:
120128
docstr_raises = set(docstr_info.raises)
121129

122130
# Check for multiple raises sections
@@ -134,6 +142,14 @@ def check(
134142
if exc and exc.name not in docstr_raises
135143
)
136144

145+
# Check for duplicate exceptions in raises
146+
exc_occurrences = Counter(docstr_info.raises)
147+
yield from (
148+
types_.Problem(docstr_node.lineno, docstr_node.col_offset, DUPLICATE_EXC_MSG % exc)
149+
for exc, occurrences in exc_occurrences.items()
150+
if occurrences > 1
151+
)
152+
137153
# Check for exceptions in the docstring that are not raised unless function has a raises
138154
# without an exception
139155
if not has_raise_no_value:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "flake8-docstrings-complete"
3-
version = "1.0.4"
3+
version = "1.1.0"
44
description = "A linter that checks docstrings are complete"
55
authors = ["David Andersson <[email protected]>"]
66
license = "Apache 2.0"

0 commit comments

Comments
 (0)