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

Commit

Permalink
Prevent D103 errors when the function is decorated with @overload and…
Browse files Browse the repository at this point in the history
… 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.
  • Loading branch information
theyuvalraz authored Sep 13, 2020
1 parent d85735e commit 7921a6d
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 11 deletions.
13 changes: 13 additions & 0 deletions docs/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,24 @@ Major Updates
New Features

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

Bug Fixes

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

5.1.2 - September 13th, 2020
----------------------------

New Features

* Methods, Functions and Nested functions that have a docstring now throw D418 (#511).
* Methods decorated with @overload no longer reported as D102.
* Functions and nested functions decorated with @overload no longer reported as D103.


5.1.1 - August 29th, 2020
---------------------------

Expand Down
34 changes: 25 additions & 9 deletions src/pydocstyle/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import ast
import string
import sys
import textwrap
import tokenize as tk
from collections import namedtuple
from itertools import chain, takewhile
Expand Down Expand Up @@ -204,17 +202,23 @@ def check_docstring_missing(self, definition, docstring):
Module: violations.D100,
Class: violations.D101,
NestedClass: violations.D106,
Method: (
lambda: violations.D105()
if definition.is_magic
Method: lambda: violations.D105()
if definition.is_magic
else (
violations.D107()
if definition.is_init
else (
violations.D107()
if definition.is_init
else violations.D102()
violations.D102()
if not definition.is_overload
else None
)
),
Function: violations.D103,
NestedFunction: violations.D103,
Function: (
lambda: violations.D103()
if not definition.is_overload
else None
),
Package: violations.D104,
}
return codes[type(definition)]()
Expand Down Expand Up @@ -544,6 +548,18 @@ def check_capitalized(self, function, docstring):
if first_word != first_word.capitalize():
return violations.D403(first_word.capitalize(), first_word)

@check_for(Function)
def check_if_needed(self, function, docstring):
"""D418: Function decorated with @overload shouldn't contain a docstring.
Functions that are decorated with @overload are definitions,
and are for the benefit of the type checker only,
since they will be overwritten by the non-@overload-decorated definition.
"""
if docstring and function.is_overload:
return violations.D418()

@check_for(Definition)
def check_starts_with_this(self, function, docstring):
"""D404: First word of the docstring should not be `This`.
Expand Down
8 changes: 8 additions & 0 deletions src/pydocstyle/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,14 @@ def is_public(self):
else:
return not self.name.startswith('_')

@property
def is_overload(self):
"""Return True iff the method decorated with overload."""
for decorator in self.decorators:
if decorator.name == "overload":
return True
return False

@property
def is_test(self):
"""Return True if this function is a test function/method.
Expand Down
6 changes: 6 additions & 0 deletions src/pydocstyle/violations.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,11 @@ def to_rst(cls) -> str:
'argument(s) {0} are missing descriptions in {1!r} docstring',
)

D418 = D4xx.create_error(
'D418',
'Function/ Method decorated with @overload shouldn\'t contain a docstring',
)


class AttrDict(dict):
def __getattr__(self, item: str) -> Any:
Expand Down Expand Up @@ -441,6 +446,7 @@ def __getattr__(self, item: str) -> Any:
'D415',
'D416',
'D417',
'D418',
},
'numpy': all_errors
- {
Expand Down
61 changes: 60 additions & 1 deletion src/tests/test_cases/test.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# No docstring, so we can test D100
from functools import wraps
import os
import sys
from .expected import Expectation
from typing import overload


expectation = Expectation()
Expand All @@ -25,6 +25,23 @@ def method(self=None):
def _ok_since_private(self=None):
pass

@overload
def overloaded_method(self, a: int) -> str:
...

@overload
def overloaded_method(self, a: str) -> str:
"""Foo bar documentation."""
...

def overloaded_method(a):
"""Foo bar documentation."""
return str(a)

expect('overloaded_method',
"D418: Function/ Method decorated with @overload"
" shouldn't contain a docstring")

@expect('D102: Missing docstring in public method')
def __new__(self=None):
pass
Expand Down Expand Up @@ -53,6 +70,48 @@ def nested():
''


def function_with_nesting():
"""Foo bar documentation."""
@overload
def nested_overloaded_func(a: int) -> str:
...

@overload
def nested_overloaded_func(a: str) -> str:
"""Foo bar documentation."""
...

def nested_overloaded_func(a):
"""Foo bar documentation."""
return str(a)


expect('nested_overloaded_func',
"D418: Function/ Method decorated with @overload"
" shouldn't contain a docstring")


@overload
def overloaded_func(a: int) -> str:
...


@overload
def overloaded_func(a: str) -> str:
"""Foo bar documentation."""
...


def overloaded_func(a):
"""Foo bar documentation."""
return str(a)


expect('overloaded_func',
"D418: Function/ Method decorated with @overload"
" shouldn't contain a docstring")


@expect('D200: One-line docstring should fit on one line with quotes '
'(found 3)')
@expect('D212: Multi-line docstring summary should start at the first line')
Expand Down
184 changes: 183 additions & 1 deletion src/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from collections import namedtuple

import os
import sys
import shlex
import shutil
import pytest
Expand Down Expand Up @@ -502,6 +501,189 @@ def foo():
in err)


def test_overload_function(env):
"""Functions decorated with @overload trigger D418 error."""
with env.open('example.py', 'wt') as example:
example.write(textwrap.dedent('''\
from typing import overload
@overload
def overloaded_func(a: int) -> str:
...
@overload
def overloaded_func(a: str) -> str:
"""Foo bar documentation."""
...
def overloaded_func(a):
"""Foo bar documentation."""
return str(a)
'''))
env.write_config(ignore="D100")
out, err, code = env.invoke()
assert code == 1
assert 'D418' in out
assert 'D103' not in out


def test_overload_method(env):
"""Methods decorated with @overload trigger D418 error."""
with env.open('example.py', 'wt') as example:
example.write(textwrap.dedent('''\
from typing import overload
class ClassWithMethods:
@overload
def overloaded_method(a: int) -> str:
...
@overload
def overloaded_method(a: str) -> str:
"""Foo bar documentation."""
...
def overloaded_method(a):
"""Foo bar documentation."""
return str(a)
'''))
env.write_config(ignore="D100")
out, err, code = env.invoke()
assert code == 1
assert 'D418' in out
assert 'D102' not in out
assert 'D103' not in out


def test_overload_method_valid(env):
"""Valid case for overload decorated Methods.
This shouldn't throw any errors.
"""
with env.open('example.py', 'wt') as example:
example.write(textwrap.dedent('''\
from typing import overload
class ClassWithMethods:
"""Valid docstring in public Class."""
@overload
def overloaded_method(a: int) -> str:
...
@overload
def overloaded_method(a: str) -> str:
...
def overloaded_method(a):
"""Foo bar documentation."""
return str(a)
'''))
env.write_config(ignore="D100, D203")
out, err, code = env.invoke()
assert code == 0


def test_overload_function_valid(env):
"""Valid case for overload decorated functions.
This shouldn't throw any errors.
"""
with env.open('example.py', 'wt') as example:
example.write(textwrap.dedent('''\
from typing import overload
@overload
def overloaded_func(a: int) -> str:
...
@overload
def overloaded_func(a: str) -> str:
...
def overloaded_func(a):
"""Foo bar documentation."""
return str(a)
'''))
env.write_config(ignore="D100")
out, err, code = env.invoke()
assert code == 0


def test_overload_nested_function(env):
"""Nested functions decorated with @overload trigger D418 error."""
with env.open('example.py', 'wt') as example:
example.write(textwrap.dedent('''\
from typing import overload
def function_with_nesting():
"""Valid docstring in public function."""
@overload
def overloaded_func(a: int) -> str:
...
@overload
def overloaded_func(a: str) -> str:
"""Foo bar documentation."""
...
def overloaded_func(a):
"""Foo bar documentation."""
return str(a)
'''))
env.write_config(ignore="D100")
out, err, code = env.invoke()
assert code == 1
assert 'D418' in out
assert 'D103' not in out


def test_overload_nested_function_valid(env):
"""Valid case for overload decorated nested functions.
This shouldn't throw any errors.
"""
with env.open('example.py', 'wt') as example:
example.write(textwrap.dedent('''\
from typing import overload
def function_with_nesting():
"""Adding a docstring to a function."""
@overload
def overloaded_func(a: int) -> str:
...
@overload
def overloaded_func(a: str) -> str:
...
def overloaded_func(a):
"""Foo bar documentation."""
return str(a)
'''))
env.write_config(ignore="D100")
out, err, code = env.invoke()
assert code == 0


def test_conflicting_select_ignore_config(env):
"""Test that select and ignore are mutually exclusive."""
env.write_config(select="D100", ignore="D101")
Expand Down

0 comments on commit 7921a6d

Please sign in to comment.