-
Notifications
You must be signed in to change notification settings - Fork 7
/
module_analyzer.py
206 lines (158 loc) · 5.57 KB
/
module_analyzer.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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
"""
AST analyzer for `ast.Module` records.
"""
from typing import List, Union
import handsdown.ast_parser.smart_ast as ast
from handsdown.ast_parser.analyzers.base_analyzer import BaseAnalyzer
from handsdown.ast_parser.type_defs import ASTFunctionDef, ASTImport
class ModuleAnalyzer(BaseAnalyzer):
"""
AST analyzer for `ast.Module` records.
"""
def __init__(self) -> None:
super().__init__()
self.all_names: List[str] = []
self.import_nodes: List[ASTImport] = []
self.function_nodes: List[ASTFunctionDef] = []
self.attribute_nodes: List[Union[ast.Assign, ast.AnnAssign]] = []
self.class_nodes: List[ast.ClassDef] = []
def visit_Import(self, node: ast.Import) -> None:
"""
Parse info about module `import ...` statements.
Adds `node` to `import_nodes`.
Examples::
import my_module
import my_module as my
import my_module.my_class
import my_module.my_class as my_class
Arguments:
node -- AST node.
"""
self.import_nodes.append(node)
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
"""
Parse info about module `import ... from ...` statements.
Adds `node` to `import_nodes`.
Examples::
from my_module import my_class
from my_module import my_class as new_class
Arguments:
node -- AST node.
"""
self.import_nodes.append(node)
def visit_ClassDef(self, node: ast.ClassDef) -> None:
"""
Parse info about module `class ...` statements.
Adds `node` entry to `class_nodes`.
Skips nodes with names starting with `_`.
Examples:
```python
class MyClass():
pass
```
Arguments:
node -- AST node.
"""
name = node.name
docstring = self.get_docstring(node)
# skip private classes with no docstring
if name.startswith("_") and not docstring:
return
self.class_nodes.append(node)
def _visit_FunctionDef(self, node: ASTFunctionDef) -> None:
name = node.name
docstring = self.get_docstring(node)
# skip private functions with no docstring
if name.startswith("_") and not docstring:
return
self.function_nodes.append(node)
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
"""
Parse info about module `def ...` statements.
Adds `node` entry to `function_nodes`.
Skips nodes with names starting with `_`.
Examples:
```python
def my_func(arg1):
return arg1
```
Arguments:
node -- AST node.
"""
return self._visit_FunctionDef(node)
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
"""
Parse info about module `def ...` statements.
Adds `node` entry to `function_nodes`.
Skips nodes with names starting with `_`.
Examples:
```python
async def my_func(arg1):
return await arg1
```
Arguments:
node -- AST node.
"""
return self._visit_FunctionDef(node)
def visit_Assign(self, node: ast.Assign) -> None:
"""
Parse info about module attribute statements.
Adds new `ast.Assign` entry to `attribute_nodes`.
Skips assignments to anything other than a new variable.
Skips multiple assignments.
Skips assignments with names starting with `_`.
Parses `__all__` and add all values to `all_names`
Examples:
```python
MY_MODULE_ATTR = 'value'
my_attr = "value"
__all__ = ['MyClass', 'my_func']
# these entries are skipped
_MY_MODULE_ATTR = "value"
multi_attr_1, multi_attr_2 = [1, 2]
my_object.name = "value"
__all__ = all_list
```
Arguments:
node -- AST node.
"""
# skip multiple assignments
if len(node.targets) != 1:
return
# skip complex assignments
if not isinstance(node.targets[0], ast.Name):
return
name = node.targets[0].id
# gather public names from `__all__` directive
if name == "__all__" and isinstance(node.value, (ast.List, ast.Tuple, ast.Set)):
for element in node.value.elts:
if isinstance(element, (ast.Str, ast.Constant)):
value = element.s
if isinstance(value, bytes):
value = value.decode("utf-8")
self.all_names.append(value)
# skip private attributes
if name.startswith("_"):
return
self.attribute_nodes.append(node)
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
"""
Parse info about module attribute statements.
Adds new `ast.AnnAssign` entry to `attribute_nodes`.
Skips assignments with names starting with `_`.
Examples:
```python
MY_MODULE_INT: int
MY_MODULE_ATTR: str = 'value'
```
Arguments:
node -- AST node.
"""
# skip complex assignments
if not isinstance(node.target, ast.Name):
return
name = node.target.id
# skip private attributes
if name.startswith("_"):
return
self.attribute_nodes.append(node)