/
find_missing_docstrings.py
86 lines (66 loc) · 2.71 KB
/
find_missing_docstrings.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#! /usr/bin/env python3
""" A simple script to list members of a module missing docstrings """
import argparse
import ast
from pathlib import Path
from typing import Iterable
try:
from colors import color
except Exception:
def color(s: str, *args, **kwargs) -> str:
return s
_DOCUMENTABLE = (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('file', help="python module source file to search for missing docstrings")
parser.add_argument('-p', '--private', action='store_true', help="include private members")
parser.add_argument('-m', '--magic', action='store_true', help="include magic members")
parser.add_argument('-q', '--quiet', action='store_true', help="output nothing if no members are missing docstrings")
args = parser.parse_args()
def allow_name(name: str) -> bool:
if name is not None:
if name.startswith('__') and name.endswith('__'):
return args.magic
elif name.startswith('_'):
return args.private
else:
return True
def iter_fields(node: ast.AST) -> Iterable[ast.AST]:
for fieldname in node._fields:
yield getattr(node, fieldname)
def iter_missing_docstrings(node: ast.AST) -> Iterable:
if 'name' not in node._fields or allow_name(node.name):
if isinstance(node, _DOCUMENTABLE) and ast.get_docstring(node) is None:
yield node
for field in iter_fields(node):
if isinstance(field, list):
for item in field:
if isinstance(item, ast.AST):
yield from iter_missing_docstrings(item)
elif isinstance(field, ast.AST):
yield from iter_missing_docstrings(field)
def format_node(node: ast.AST):
if isinstance(node, ast.Module):
name_str = Path(args.file).stem
help_str = f"{args.file}"
type_str = "module"
else:
name_str = node.name
help_str = f"{args.file} L{color(str(node.lineno), fg='white')}:{color(str(node.col_offset), fg='white')}"
if isinstance(node, ast.ClassDef):
type_str = "class"
elif isinstance(node, ast.FunctionDef):
type_str = "function"
elif isinstance(node, ast.AsyncFunctionDef):
type_str = "coroutine"
return f"{type_str} '{color(name_str, fg='cyan')}' ({help_str})"
with open(args.file, 'r') as f:
source = f.read()
tree = ast.parse(source, mode='exec')
nodes = list(iter_missing_docstrings(tree))
if len(nodes) == 0:
if not args.quiet:
print("No members are missing docstrings.")
else:
print("Members missing docstrings:")
for node in nodes:
print(" " + format_node(node))