-
Notifications
You must be signed in to change notification settings - Fork 34
/
ast_visitor.py
131 lines (109 loc) · 4.34 KB
/
ast_visitor.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
# pylint: disable=invalid-name
"""Custom Abstract Syntax Tree visitors"""
import ast
import logging
import sys
from code_info import CodeInfo, ClassInfo
logger = logging.getLogger() # (__name__)
class TreeVisitor(ast.NodeVisitor):
"""`ast.NodeVisitor` is the primary tool for ‘scanning’ the tree.
To use it, subclass it and override methods visit_Foo, corresponding to the
node classes (see Meet the Nodes).
>>> visitor = TreeVisitor("examples/person.py")
>>> visitor.parse(
>>> visitor.visit_tree()
"""
# List to put the class data.
def __init__(self, srcfile, context=None):
self.srcfile = srcfile
self.context = context
self.classinfo = None
self.moduleinfo = None
self.constructor = False
self.tree = None
def parse(self, errormsg=None):
"""Use AST to parse the source file."""
try:
with open(self.srcfile) as src:
self.tree = ast.parse(src.read())
return self.tree
except FileNotFoundError as err:
sys.stderr.write(str(err) + ", skipping\n")
except SyntaxError as see:
sys.stderr.write('Syntax error in {0}:{1}:{2}: {3}'.format(
self.srcfile, see.lineno, see.offset, see.text))
if errormsg:
sys.stderr.write(errormsg + "\n")
return False
def visit_tree(self):
"""Visits the parsed tree."""
return self.visit(self.tree)
def visit_Module(self, node):
"""
Overrides AST module visitor (top level).
:param node ast.Node : The parsed code
"""
# Instanciate moduleinfo if required
# self.moduleinfo = context.getboolean(
# 'module','write-globals', fallback=False) and CodeInfo() or None
self.moduleinfo = CodeInfo() if self.context.opt_globals() else None
# Run through all children of the module
for child in node.body:
self.visit(child)
if self.moduleinfo:
self.moduleinfo.done(self.context)
def visit_ClassDef(self, node):
"""
Overrides AST class definition visitor.
:param node: The node of the class.
"""
# push context
prev_classinfo = self.classinfo
self.classinfo = ClassInfo(node)
# Run through all children of the class definition
for child in node.body:
self.visit(child)
# finished class parsing, report it now.
if self.classinfo:
self.classinfo.done(self.context)
# restore previous context
self.classinfo = prev_classinfo
def visit_FunctionDef(self, node):
"Overrides AST function definition visitor"
if self.classinfo:
# Check if this s the constructor.
if node.name == '__init__':
self.constructor = True
# Find all assignment expressions in the constructor.
for code in node.body:
self.visit(code)
self.constructor = False
self.classinfo.add_method(node)
elif self.moduleinfo:
self.moduleinfo.add_function(node)
def visit_Assign(self, node):
"Overrides AST assignment statement visitor"
# FIXME assignments to imported names may incorrectly report variable declaration
# pylint: disable=unnecessary-lambda
if self.constructor:
# Find attributes since we want "self." + "something"
for target in node.targets:
if isinstance(target, ast.Attribute):
# If the value is a name and its id is self.
if isinstance(target.value, ast.Name):
if target.value.id == 'self':
self.classinfo.add_member(target.attr)
else:
if self.classinfo:
# Store as class variable (shared by all instances)
fn = lambda x: self.classinfo.add_classvar(x)
elif self.moduleinfo:
# Store as global variable
fn = lambda x: self.moduleinfo.add_variable(x)
# pylint disable=unnecessary-lambda
else:
return
for target in node.targets:
# keep only simple names
if isinstance(target, ast.Name):
fn(target.id)