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

Fix decorator parsing for async functions #577

Merged
merged 8 commits into from
Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions docs/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Release Notes
**pydocstyle** version numbers follow the
`Semantic Versioning <http://semver.org/>`_ specification.

6.2.3 - January 8th, 2023
---------------------------

Bug Fixes

* Fix decorator parsing for async function. Resolves some false positives
with async functions and ``overload``. (#577)

6.2.2 - January 3rd, 2023
---------------------------
Expand Down
1 change: 1 addition & 0 deletions src/pydocstyle/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ def parse_decorators(self):
self.current.value,
)
if self.current.kind == tk.NAME and self.current.value in [
'async',
'def',
'class',
]:
Expand Down
15 changes: 15 additions & 0 deletions src/tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@ def some_method(self):
assert 'first_decorator' == decorators[0].name
assert '' == decorators[0].arguments

def test_parse_async_function_decorator(self):
"""Decorators for async functions are also accumulated."""
code = textwrap.dedent("""\
@first_decorator
async def some_method(self):
pass
""")

module = checker.parse(io.StringIO(code), 'dummy.py')
decorators = module.children[0].decorators

assert 1 == len(decorators)
assert 'first_decorator' == decorators[0].name
assert '' == decorators[0].arguments

def test_parse_method_nested_decorator(self):
"""Method decorators are accumulated for nested methods."""
code = textwrap.dedent("""\
Expand Down
60 changes: 60 additions & 0 deletions src/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,36 @@ def overloaded_func(a):
assert 'D103' not in out


def test_overload_async_function(env):
"""Async functions decorated with @overload trigger D418 error."""
with env.open('example.py', 'wt') as example:
example.write(textwrap.dedent('''\
from typing import overload


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


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


async 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:
Expand Down Expand Up @@ -714,6 +744,36 @@ def overloaded_func(a):
assert code == 0


def test_overload_async_function_valid(env):
"""Valid case for overload decorated async functions.

This shouldn't throw any errors.
"""
with env.open('example.py', 'wt') as example:
example.write(textwrap.dedent('''\
from typing import overload


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


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


async 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:
Expand Down