Skip to content
This repository was archived by the owner on Nov 3, 2023. It is now read-only.

Commit 7921a6d

Browse files
authored
Prevent D103 errors when the function is decorated with @overload and add D418 (#511)
* Preventing the D103 error when the function is decorated with @overload. Added an is_overload method in the function class(parser.py). Added an if statement so that the D103 error will not trigger when decorated with @overload(checker.py) Added some tests to see that it's working correctly. * Preventing the D103 error when the function is decorated with @overload. Added an is_overload method in the function class(parser.py). Added an if statement so that the D103 error will not trigger when decorated with @overload(checker.py) Added some tests to see that it's working correctly. * Added an is_overload method in the function class(parser.py). Added an if statement so that the D103 error will not trigger when decorated with @overload(checker.py) Added some tests to see that it's working correctly. * Fixing overload test. * Fixing overload test. Running isort src/pydocstyle * Added D418 Error: Function decorated with @overload shouldn\'t contain a docstring. * Overloaded functions shouldn't have a definition. * Tests for D418 error: Functions decorated with @overload * Tests for D418 error: Functions decorated with @overload * Tests for D418 error: Functions decorated with @overload * Added Tests for nested_functions/methods that are decorated with @overload checker is also preventing the 102 error in methods that are decorated with @overload. (checker.py) Any suggestions on how to write those if statements more elegantly? I really don't like the nested if statement. * Added Tests for valid overloaded functions, valid overloaded Method and overloaded Methods with D418 Error. * Added Tests for valid overloaded nested functions. * release_notes.rst updated. * release_notes.rst updated.
1 parent d85735e commit 7921a6d

File tree

6 files changed

+295
-11
lines changed

6 files changed

+295
-11
lines changed

docs/release_notes.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,24 @@ Major Updates
1515
New Features
1616

1717
* Add flag to disable `# noqa` comment processing in API (#485).
18+
* Methods, Functions and Nested functions that have a docstring now throw D418 (#511).
19+
* Methods decorated with @overload no longer reported as D102 (#511).
20+
* Functions and nested functions decorated with @overload no longer reported as D103 (#511).
1821

1922
Bug Fixes
2023

2124
* Treat "package" as an imperative verb for D401 (#356).
2225

26+
5.1.2 - September 13th, 2020
27+
----------------------------
28+
29+
New Features
30+
31+
* Methods, Functions and Nested functions that have a docstring now throw D418 (#511).
32+
* Methods decorated with @overload no longer reported as D102.
33+
* Functions and nested functions decorated with @overload no longer reported as D103.
34+
35+
2336
5.1.1 - August 29th, 2020
2437
---------------------------
2538

src/pydocstyle/checker.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import ast
44
import string
5-
import sys
6-
import textwrap
75
import tokenize as tk
86
from collections import namedtuple
97
from itertools import chain, takewhile
@@ -204,17 +202,23 @@ def check_docstring_missing(self, definition, docstring):
204202
Module: violations.D100,
205203
Class: violations.D101,
206204
NestedClass: violations.D106,
207-
Method: (
208-
lambda: violations.D105()
209-
if definition.is_magic
205+
Method: lambda: violations.D105()
206+
if definition.is_magic
207+
else (
208+
violations.D107()
209+
if definition.is_init
210210
else (
211-
violations.D107()
212-
if definition.is_init
213-
else violations.D102()
211+
violations.D102()
212+
if not definition.is_overload
213+
else None
214214
)
215215
),
216-
Function: violations.D103,
217216
NestedFunction: violations.D103,
217+
Function: (
218+
lambda: violations.D103()
219+
if not definition.is_overload
220+
else None
221+
),
218222
Package: violations.D104,
219223
}
220224
return codes[type(definition)]()
@@ -544,6 +548,18 @@ def check_capitalized(self, function, docstring):
544548
if first_word != first_word.capitalize():
545549
return violations.D403(first_word.capitalize(), first_word)
546550

551+
@check_for(Function)
552+
def check_if_needed(self, function, docstring):
553+
"""D418: Function decorated with @overload shouldn't contain a docstring.
554+
555+
Functions that are decorated with @overload are definitions,
556+
and are for the benefit of the type checker only,
557+
since they will be overwritten by the non-@overload-decorated definition.
558+
559+
"""
560+
if docstring and function.is_overload:
561+
return violations.D418()
562+
547563
@check_for(Definition)
548564
def check_starts_with_this(self, function, docstring):
549565
"""D404: First word of the docstring should not be `This`.

src/pydocstyle/parser.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ def is_public(self):
210210
else:
211211
return not self.name.startswith('_')
212212

213+
@property
214+
def is_overload(self):
215+
"""Return True iff the method decorated with overload."""
216+
for decorator in self.decorators:
217+
if decorator.name == "overload":
218+
return True
219+
return False
220+
213221
@property
214222
def is_test(self):
215223
"""Return True if this function is a test function/method.

src/pydocstyle/violations.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,11 @@ def to_rst(cls) -> str:
411411
'argument(s) {0} are missing descriptions in {1!r} docstring',
412412
)
413413

414+
D418 = D4xx.create_error(
415+
'D418',
416+
'Function/ Method decorated with @overload shouldn\'t contain a docstring',
417+
)
418+
414419

415420
class AttrDict(dict):
416421
def __getattr__(self, item: str) -> Any:
@@ -441,6 +446,7 @@ def __getattr__(self, item: str) -> Any:
441446
'D415',
442447
'D416',
443448
'D417',
449+
'D418',
444450
},
445451
'numpy': all_errors
446452
- {

src/tests/test_cases/test.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# No docstring, so we can test D100
22
from functools import wraps
33
import os
4-
import sys
54
from .expected import Expectation
5+
from typing import overload
66

77

88
expectation = Expectation()
@@ -25,6 +25,23 @@ def method(self=None):
2525
def _ok_since_private(self=None):
2626
pass
2727

28+
@overload
29+
def overloaded_method(self, a: int) -> str:
30+
...
31+
32+
@overload
33+
def overloaded_method(self, a: str) -> str:
34+
"""Foo bar documentation."""
35+
...
36+
37+
def overloaded_method(a):
38+
"""Foo bar documentation."""
39+
return str(a)
40+
41+
expect('overloaded_method',
42+
"D418: Function/ Method decorated with @overload"
43+
" shouldn't contain a docstring")
44+
2845
@expect('D102: Missing docstring in public method')
2946
def __new__(self=None):
3047
pass
@@ -53,6 +70,48 @@ def nested():
5370
''
5471

5572

73+
def function_with_nesting():
74+
"""Foo bar documentation."""
75+
@overload
76+
def nested_overloaded_func(a: int) -> str:
77+
...
78+
79+
@overload
80+
def nested_overloaded_func(a: str) -> str:
81+
"""Foo bar documentation."""
82+
...
83+
84+
def nested_overloaded_func(a):
85+
"""Foo bar documentation."""
86+
return str(a)
87+
88+
89+
expect('nested_overloaded_func',
90+
"D418: Function/ Method decorated with @overload"
91+
" shouldn't contain a docstring")
92+
93+
94+
@overload
95+
def overloaded_func(a: int) -> str:
96+
...
97+
98+
99+
@overload
100+
def overloaded_func(a: str) -> str:
101+
"""Foo bar documentation."""
102+
...
103+
104+
105+
def overloaded_func(a):
106+
"""Foo bar documentation."""
107+
return str(a)
108+
109+
110+
expect('overloaded_func',
111+
"D418: Function/ Method decorated with @overload"
112+
" shouldn't contain a docstring")
113+
114+
56115
@expect('D200: One-line docstring should fit on one line with quotes '
57116
'(found 3)')
58117
@expect('D212: Multi-line docstring summary should start at the first line')

src/tests/test_integration.py

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from collections import namedtuple
44

55
import os
6-
import sys
76
import shlex
87
import shutil
98
import pytest
@@ -502,6 +501,189 @@ def foo():
502501
in err)
503502

504503

504+
def test_overload_function(env):
505+
"""Functions decorated with @overload trigger D418 error."""
506+
with env.open('example.py', 'wt') as example:
507+
example.write(textwrap.dedent('''\
508+
from typing import overload
509+
510+
511+
@overload
512+
def overloaded_func(a: int) -> str:
513+
...
514+
515+
516+
@overload
517+
def overloaded_func(a: str) -> str:
518+
"""Foo bar documentation."""
519+
...
520+
521+
522+
def overloaded_func(a):
523+
"""Foo bar documentation."""
524+
return str(a)
525+
526+
'''))
527+
env.write_config(ignore="D100")
528+
out, err, code = env.invoke()
529+
assert code == 1
530+
assert 'D418' in out
531+
assert 'D103' not in out
532+
533+
534+
def test_overload_method(env):
535+
"""Methods decorated with @overload trigger D418 error."""
536+
with env.open('example.py', 'wt') as example:
537+
example.write(textwrap.dedent('''\
538+
from typing import overload
539+
540+
class ClassWithMethods:
541+
@overload
542+
def overloaded_method(a: int) -> str:
543+
...
544+
545+
546+
@overload
547+
def overloaded_method(a: str) -> str:
548+
"""Foo bar documentation."""
549+
...
550+
551+
552+
def overloaded_method(a):
553+
"""Foo bar documentation."""
554+
return str(a)
555+
556+
'''))
557+
env.write_config(ignore="D100")
558+
out, err, code = env.invoke()
559+
assert code == 1
560+
assert 'D418' in out
561+
assert 'D102' not in out
562+
assert 'D103' not in out
563+
564+
565+
def test_overload_method_valid(env):
566+
"""Valid case for overload decorated Methods.
567+
568+
This shouldn't throw any errors.
569+
"""
570+
with env.open('example.py', 'wt') as example:
571+
example.write(textwrap.dedent('''\
572+
from typing import overload
573+
574+
class ClassWithMethods:
575+
"""Valid docstring in public Class."""
576+
577+
@overload
578+
def overloaded_method(a: int) -> str:
579+
...
580+
581+
582+
@overload
583+
def overloaded_method(a: str) -> str:
584+
...
585+
586+
587+
def overloaded_method(a):
588+
"""Foo bar documentation."""
589+
return str(a)
590+
591+
'''))
592+
env.write_config(ignore="D100, D203")
593+
out, err, code = env.invoke()
594+
assert code == 0
595+
596+
597+
def test_overload_function_valid(env):
598+
"""Valid case for overload decorated functions.
599+
600+
This shouldn't throw any errors.
601+
"""
602+
with env.open('example.py', 'wt') as example:
603+
example.write(textwrap.dedent('''\
604+
from typing import overload
605+
606+
607+
@overload
608+
def overloaded_func(a: int) -> str:
609+
...
610+
611+
612+
@overload
613+
def overloaded_func(a: str) -> str:
614+
...
615+
616+
617+
def overloaded_func(a):
618+
"""Foo bar documentation."""
619+
return str(a)
620+
621+
'''))
622+
env.write_config(ignore="D100")
623+
out, err, code = env.invoke()
624+
assert code == 0
625+
626+
627+
def test_overload_nested_function(env):
628+
"""Nested functions decorated with @overload trigger D418 error."""
629+
with env.open('example.py', 'wt') as example:
630+
example.write(textwrap.dedent('''\
631+
from typing import overload
632+
633+
def function_with_nesting():
634+
"""Valid docstring in public function."""
635+
@overload
636+
def overloaded_func(a: int) -> str:
637+
...
638+
639+
640+
@overload
641+
def overloaded_func(a: str) -> str:
642+
"""Foo bar documentation."""
643+
...
644+
645+
646+
def overloaded_func(a):
647+
"""Foo bar documentation."""
648+
return str(a)
649+
'''))
650+
env.write_config(ignore="D100")
651+
out, err, code = env.invoke()
652+
assert code == 1
653+
assert 'D418' in out
654+
assert 'D103' not in out
655+
656+
657+
def test_overload_nested_function_valid(env):
658+
"""Valid case for overload decorated nested functions.
659+
660+
This shouldn't throw any errors.
661+
"""
662+
with env.open('example.py', 'wt') as example:
663+
example.write(textwrap.dedent('''\
664+
from typing import overload
665+
666+
def function_with_nesting():
667+
"""Adding a docstring to a function."""
668+
@overload
669+
def overloaded_func(a: int) -> str:
670+
...
671+
672+
673+
@overload
674+
def overloaded_func(a: str) -> str:
675+
...
676+
677+
678+
def overloaded_func(a):
679+
"""Foo bar documentation."""
680+
return str(a)
681+
'''))
682+
env.write_config(ignore="D100")
683+
out, err, code = env.invoke()
684+
assert code == 0
685+
686+
505687
def test_conflicting_select_ignore_config(env):
506688
"""Test that select and ignore are mutually exclusive."""
507689
env.write_config(select="D100", ignore="D101")

0 commit comments

Comments
 (0)