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

Preventing the D103 error when the function is decorated with @overload. #511

Merged
merged 17 commits into from
Sep 13, 2020
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
theyuvalraz marked this conversation as resolved.
Show resolved Hide resolved
Function: (
theyuvalraz marked this conversation as resolved.
Show resolved Hide resolved
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:
theyuvalraz marked this conversation as resolved.
Show resolved Hide resolved
"""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:
theyuvalraz marked this conversation as resolved.
Show resolved Hide resolved
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):
theyuvalraz marked this conversation as resolved.
Show resolved Hide resolved
"""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."""
theyuvalraz marked this conversation as resolved.
Show resolved Hide resolved
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