Skip to content

Commit bc0f257

Browse files
caffeinatedMikeMiWeiss
authored andcommitted
Added AsyncFunctionDef visitor to make compatible with async functions (HunterMcGushion#116)
Allows the tool to properly detect async functions and factor in their docstrings in coverage/missing reports
1 parent 6b65d3b commit bc0f257

File tree

6 files changed

+46
-11
lines changed

6 files changed

+46
-11
lines changed

docstr_coverage/visitor.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
"""This module handles traversing abstract syntax trees to check for docstrings"""
22
import re
33
import tokenize
4-
from ast import ClassDef, FunctionDef, Module, NodeVisitor, get_docstring
4+
from ast import (
5+
AsyncFunctionDef,
6+
ClassDef,
7+
FunctionDef,
8+
Module,
9+
NodeVisitor,
10+
get_docstring,
11+
)
512
from typing import Optional
613

714
ACCEPTED_EXCUSE_PATTERNS = (
@@ -37,6 +44,10 @@ def visit_FunctionDef(self, node: FunctionDef):
3744
"""Collect information regarding function/method declaration nodes"""
3845
self._visit_helper(node)
3946

47+
def visit_AsyncFunctionDef(self, node: AsyncFunctionDef):
48+
"""Collect information regarding async function/method declaration nodes"""
49+
self._visit_helper(node)
50+
4051
def _visit_helper(self, node):
4152
"""Helper method to update :attr:`DocStringCoverageVisitor.tree` with pertinent
4253
documentation information for `node`, then ensure all child nodes are

tests/excused_samples/fully_excused.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,21 @@ def function(self):
1919
def prop(self):
2020
pass
2121

22+
# docstr-coverage:excuse `...almost as lazy as async functions...`
23+
async def async_function(self):
24+
pass
25+
2226

2327
# docstr-coverage:excuse `... besides: who's checking anyways`
2428
def bar():
2529
pass
2630

2731

32+
# docstr-coverage:excuse `... also: setting up async tests suck in general`
33+
async def baz():
34+
pass
35+
36+
2837
# docstr-coverage:excuse `no one is reading this anyways`
2938
class FooBarChild(FooBar):
3039

tests/extra_samples/private_undocumented.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,11 @@ def _foo():
77

88
def __dunder():
99
pass
10+
11+
12+
async def _afoo():
13+
pass
14+
15+
16+
async def __adunder():
17+
pass

tests/sample_files/subdir_a/documented_file.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ def function(self):
1313
def another_function(self):
1414
"""This is a second regular method docstring"""
1515

16+
async def an_async_function(self):
17+
"""This is an async method docstring"""
18+
1619
@property
1720
def prop(self):
1821
"""This is a wrapped method docstring"""
@@ -32,3 +35,7 @@ def _foo():
3235

3336
def bar():
3437
"""This is another function"""
38+
39+
40+
async def baz():
41+
"""This is an async function"""

tests/test_cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,11 @@ def test_parse_ignore_names_file(path: str, expected: tuple):
230230
@pytest.mark.parametrize(
231231
["paths", "expected_output"],
232232
[
233-
[[SAMPLES_A.dirpath], "62.5"],
233+
[[SAMPLES_A.dirpath], "66.66666666666667"],
234234
[[SAMPLES_A.partial], "20.0"],
235235
[[SAMPLES_A.documented], "100.0"],
236236
[[SAMPLES_A.undocumented], "0.0"],
237-
[[SAMPLES_A.undocumented, SAMPLES_A.documented], "81.81818181818181"],
237+
[[SAMPLES_A.undocumented, SAMPLES_A.documented], "84.61538461538461"],
238238
],
239239
)
240240
@pytest.mark.parametrize("verbose_flag", [["-v", "0"], ["-v", "1"], ["-v", "2"], ["-v", "3"]])

tests/test_coverage.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def test_should_report_for_an_empty_file():
4141

4242

4343
@pytest.mark.parametrize(
44-
["file_path", "needed_count"], [(DOCUMENTED_FILE_PATH, 9), (FULLY_EXCUSED_FILE_PATH, 8)]
44+
["file_path", "needed_count"], [(DOCUMENTED_FILE_PATH, 11), (FULLY_EXCUSED_FILE_PATH, 10)]
4545
)
4646
def test_should_report_full_coverage(file_path, needed_count):
4747
result = analyze([file_path])
@@ -105,7 +105,7 @@ def test_should_report_for_multiple_files():
105105
"missing": [],
106106
"module_doc": True,
107107
"missing_count": 0,
108-
"needed_count": 9,
108+
"needed_count": 11,
109109
"coverage": 100.0,
110110
"empty": False,
111111
},
@@ -118,7 +118,7 @@ def test_should_report_for_multiple_files():
118118
"empty": True,
119119
},
120120
}
121-
assert total_results == {"missing_count": 4, "needed_count": 14, "coverage": 71.42857142857143}
121+
assert total_results == {"missing_count": 4, "needed_count": 16, "coverage": 75.0}
122122

123123

124124
def test_should_report_when_no_docs_in_a_file():
@@ -255,14 +255,14 @@ def test_skip_private():
255255
result = analyze([PRIVATE_NO_DOCS_PATH], ignore_config=ignore_config)
256256
file_results, total_results = result.to_legacy()
257257
assert file_results[PRIVATE_NO_DOCS_PATH] == {
258-
"missing": ["__dunder"],
258+
"missing": ["__dunder", "__adunder"],
259259
"module_doc": True,
260-
"missing_count": 1,
261-
"needed_count": 2,
262-
"coverage": 50.0,
260+
"missing_count": 2,
261+
"needed_count": 3,
262+
"coverage": 33.333333333333336,
263263
"empty": False,
264264
}
265-
assert total_results == {"missing_count": 1, "needed_count": 2, "coverage": 50.0}
265+
assert total_results == {"missing_count": 2, "needed_count": 3, "coverage": 33.333333333333336}
266266

267267

268268
def test_long_doc():

0 commit comments

Comments
 (0)