1+ """
2+ Traverse the AST, indicating where docstrings are missing and suggesting templates
3+ based on the AST.
4+ """
5+
16from __future__ import annotations
27
38import ast
1318
1419
1520class DocstringVisitor (ast .NodeVisitor ):
21+ """
22+ A class for indicating where docstrings are missing in a single module of source code
23+ and suggesting templates based on the AST representation.
24+
25+ Parameters
26+ ----------
27+ filename : str
28+ The file to process.
29+ converter : type[DocstringConverter] | None, optional
30+ When this is ``None``, docstrings will be reported as missing, but when this is
31+ a converter, docstring templates will be generated.
32+ verbose : bool, keyword-only, default=False
33+ Whether to run in verbose mode.
34+ """
35+
1636 def __init__ (
17- self , filename : str , converter : type [DocstringConverter ] | None = None
37+ self ,
38+ filename : str ,
39+ converter : type [DocstringConverter ] | None = None ,
40+ * ,
41+ verbose : bool = False ,
1842 ) -> None :
1943 self .source_file : Path = Path (filename ).expanduser ().resolve ()
44+ """The ``Path`` object for the source file."""
45+
2046 self .source_code : str = self .source_file .read_text ()
47+ """The source code in :attr:`.source_file` as a string."""
48+
2149 self .tree : ast .Module = ast .parse (self .source_code )
50+ """The AST for the source code in :attr:`.source_code`."""
2251
2352 self .docstrings_inspected : int = 0
53+ """The number of docstrings inspected."""
54+
2455 self .missing_docstrings : list [DocstringNode ] = []
56+ """The list of docstrings that are missing, each represented as a :class:`.DocstringNode` instance."""
2557
2658 self .module_name : str = self .source_file .stem
59+ """The name of the module, derived from the source file name (see :attr:`.source_file`)."""
60+
2761 self .stack : list [DocstringNode ] = []
62+ """The stack of docstring nodes, used to track the current context in the AST."""
2863
2964 self .docstring_converter : DocstringConverter | None = (
3065 converter (quote = not issubclass (self .__class__ , ast .NodeTransformer ))
3166 if converter
3267 else None
3368 )
69+ """The docstring converter, if templates are to be generated, otherwise ``None``."""
70+
71+ self .verbose : bool = verbose
72+ """Whether to run in verbose mode."""
3473
3574 def report_missing_docstrings (self ) -> None :
36- if not self .missing_docstrings :
37- print (f'No missing docstrings found in { self .source_file } .' )
38- else :
75+ """
76+ Report missing docstrings.
77+
78+ See Also
79+ --------
80+ :meth:`.handle_missing_docstring`
81+ This method is called for each missing docstring, and it defines any actions
82+ that should be taken upon nodes with missing docstrings, such as, suggesting
83+ a docstring template based on the source code.
84+ """
85+ if self .missing_docstrings :
3986 for docstring_node in self .missing_docstrings :
4087 print (
4188 f'{ docstring_node .fully_qualified_name } is missing a docstring' ,
4289 file = sys .stderr ,
4390 )
4491 self .handle_missing_docstring (docstring_node )
92+ elif self .verbose :
93+ print (f'No missing docstrings found in { self .source_file } .' )
94+
95+ def handle_missing_docstring (self , docstring_node : DocstringNode ) -> None :
96+ """
97+ Handle missing docstrings by suggesting a template for them when a converter is provided.
4598
46- def handle_missing_docstring (self , docstring_node : DocstringNode ) -> DocstringNode :
99+ Parameters
100+ ----------
101+ docstring_node : DocstringNode
102+ An instance of :class:`.DocstringNode`, which wraps an AST node and adds
103+ additional context relevant for Docstringify.
104+ """
47105 if self .docstring_converter :
48106 print (
49107 'Hint:' ,
@@ -53,7 +111,23 @@ def handle_missing_docstring(self, docstring_node: DocstringNode) -> DocstringNo
53111 )
54112
55113 def process_docstring (self , docstring_node : DocstringNode ) -> DocstringNode :
56- if docstring_node .docstring_required and not docstring_node .docstring :
114+ """
115+ Process a docstring node, appending it to :attr:`.missing_docstrings` if a docstring
116+ is required, but there isn't one.
117+
118+ Parameters
119+ ----------
120+ docstring_node : DocstringNode
121+ An instance of :class:`.DocstringNode`, which wraps an AST node and adds
122+ additional context relevant for Docstringify.
123+
124+ Returns
125+ -------
126+ DocstringNode
127+ An instance of :class:`.DocstringNode`, which wraps an AST node and adds
128+ additional context relevant for Docstringify.
129+ """
130+ if docstring_node .docstring_required and (not docstring_node .docstring ):
57131 self .missing_docstrings .append (docstring_node )
58132
59133 self .docstrings_inspected += 1
@@ -64,6 +138,21 @@ def visit_docstring(
64138 node : ast .AsyncFunctionDef | ast .ClassDef | ast .FunctionDef | ast .Module ,
65139 docstring_class : type [DocstringNode ],
66140 ) -> ast .AsyncFunctionDef | ast .ClassDef | ast .FunctionDef | ast .Module :
141+ """
142+ Visit an AST node by updating the stack and processing the docstring.
143+
144+ Parameters
145+ ----------
146+ node : ast.AsyncFunctionDef | ast.ClassDef | ast.FunctionDef | ast.Module
147+ The AST node to visit.
148+ docstring_class : type[DocstringNode]
149+ The class to use for creating the docstring node.
150+
151+ Returns
152+ -------
153+ ast.AsyncFunctionDef | ast.ClassDef | ast.FunctionDef | ast.Module
154+ The AST node that was visited.
155+ """
67156 docstring_node = docstring_class (
68157 node ,
69158 self .module_name ,
@@ -79,24 +168,90 @@ def visit_docstring(
79168 return docstring_node .ast_node
80169
81170 def visit_Module (self , node : ast .Module ) -> ast .Module : # noqa: N802
171+ """
172+ Visit an :class:`ast.Module` node.
173+
174+ Parameters
175+ ----------
176+ node : ast.Module
177+ The AST node to visit.
178+
179+ Returns
180+ -------
181+ ast.Module
182+ The visited AST node.
183+ """
82184 return self .visit_docstring (node , DocstringNode )
83185
84186 def visit_ClassDef (self , node : ast .ClassDef ) -> ast .ClassDef : # noqa: N802
187+ """
188+ Visit an :class:`ast.ClassDef` node.
189+
190+ Parameters
191+ ----------
192+ node : ast.ClassDef
193+ The AST node to visit.
194+
195+ Returns
196+ -------
197+ ast.ClassDef
198+ The visited AST node.
199+ """
85200 return self .visit_docstring (node , DocstringNode )
86201
87202 def visit_FunctionDef (self , node : ast .FunctionDef ) -> ast .FunctionDef : # noqa: N802
203+ """
204+ Visit an :class:`ast.FunctionDef` node.
205+
206+ Parameters
207+ ----------
208+ node : ast.FunctionDef
209+ The AST node to visit.
210+
211+ Returns
212+ -------
213+ ast.FunctionDef
214+ The visited AST node.
215+ """
88216 return self .visit_docstring (node , FunctionDocstringNode )
89217
90218 def visit_AsyncFunctionDef ( # noqa: N802
91219 self , node : ast .AsyncFunctionDef
92220 ) -> ast .AsyncFunctionDef :
221+ """
222+ Visit an :class:`ast.AsyncFunctionDef` node.
223+
224+ Parameters
225+ ----------
226+ node : ast.AsyncFunctionDef
227+ The AST node to visit.
228+
229+ Returns
230+ -------
231+ ast.AsyncFunctionDef
232+ The visited AST node.
233+ """
93234 return self .visit_docstring (node , FunctionDocstringNode )
94235
95236 def visit_Return (self , node : ast .Return ) -> ast .Return : # noqa: N802
237+ """
238+ Visit an :class:`ast.Return` node.
239+
240+ Parameters
241+ ----------
242+ node : ast.Return
243+ The AST node to visit.
244+
245+ Returns
246+ -------
247+ ast.Return
248+ The visited AST node.
249+ """
96250 if isinstance (self .stack [- 1 ], FunctionDocstringNode ):
97251 self .stack [- 1 ].return_statements .append (node )
98252 return node
99253
100254 def process_file (self ) -> None :
255+ """Process a source code file, reporting on the missing docstrings."""
101256 self .visit (self .tree )
102257 self .report_missing_docstrings ()
0 commit comments