2019-03-06 06:27:57 +00:00
|
|
|
import collections
|
|
|
|
import os
|
2019-03-30 20:43:26 +00:00
|
|
|
import sys
|
2019-03-06 06:27:57 +00:00
|
|
|
|
|
|
|
import astroid
|
|
|
|
from . import astroid_utils
|
|
|
|
|
|
|
|
|
|
|
|
class Parser(object):
|
|
|
|
def __init__(self):
|
|
|
|
self._name_stack = []
|
|
|
|
self._encoding = None
|
|
|
|
|
|
|
|
def _get_full_name(self, name):
|
|
|
|
return ".".join(self._name_stack + [name])
|
|
|
|
|
2019-10-05 22:11:23 +00:00
|
|
|
def _decode(self, to_decode):
|
2019-08-25 22:53:58 +00:00
|
|
|
if sys.version_info < (3,) and self._encoding:
|
2019-10-06 00:00:05 +00:00
|
|
|
# pylint: disable=undefined-variable
|
2019-03-06 06:27:57 +00:00
|
|
|
try:
|
2019-10-06 00:00:05 +00:00
|
|
|
return unicode(to_decode, self._encoding)
|
2019-03-06 06:27:57 +00:00
|
|
|
except TypeError:
|
|
|
|
# The string was already in the correct format
|
|
|
|
pass
|
|
|
|
|
2019-10-05 22:11:23 +00:00
|
|
|
return to_decode
|
2019-03-06 06:27:57 +00:00
|
|
|
|
2019-10-14 21:47:52 +00:00
|
|
|
def _parse_file(self, file_path, condition):
|
2019-03-06 06:27:57 +00:00
|
|
|
directory, filename = os.path.split(file_path)
|
|
|
|
module_parts = []
|
|
|
|
if filename != "__init__.py":
|
|
|
|
module_part = os.path.splitext(filename)[0]
|
|
|
|
module_parts = [module_part]
|
|
|
|
module_parts = collections.deque(module_parts)
|
2019-10-17 07:15:41 +00:00
|
|
|
while directory and condition(directory):
|
2019-03-06 06:27:57 +00:00
|
|
|
directory, module_part = os.path.split(directory)
|
|
|
|
if module_part:
|
|
|
|
module_parts.appendleft(module_part)
|
|
|
|
|
|
|
|
module_name = ".".join(module_parts)
|
2019-04-06 18:16:40 +00:00
|
|
|
node = astroid.MANAGER.ast_from_file(file_path, module_name, source=True)
|
2019-03-06 06:27:57 +00:00
|
|
|
return self.parse(node)
|
|
|
|
|
2019-10-14 21:47:52 +00:00
|
|
|
def parse_file(self, file_path):
|
|
|
|
return self._parse_file(
|
|
|
|
file_path,
|
|
|
|
lambda directory: os.path.isfile(os.path.join(directory, "__init__.py")),
|
|
|
|
)
|
|
|
|
|
|
|
|
def parse_file_in_namespace(self, file_path, dir_root):
|
2019-10-17 07:15:41 +00:00
|
|
|
return self._parse_file(
|
|
|
|
file_path, lambda directory: os.path.abspath(directory) != dir_root
|
|
|
|
)
|
2019-10-14 21:47:52 +00:00
|
|
|
|
2019-04-22 04:45:08 +00:00
|
|
|
def parse_annassign(self, node):
|
|
|
|
return self.parse_assign(node)
|
|
|
|
|
2019-03-06 06:27:57 +00:00
|
|
|
def parse_assign(self, node):
|
|
|
|
doc = ""
|
|
|
|
doc_node = node.next_sibling()
|
|
|
|
if isinstance(doc_node, astroid.nodes.Expr) and isinstance(
|
|
|
|
doc_node.value, astroid.nodes.Const
|
|
|
|
):
|
|
|
|
doc = doc_node.value.value
|
|
|
|
|
|
|
|
type_ = "data"
|
|
|
|
if isinstance(
|
|
|
|
node.scope(), astroid.nodes.ClassDef
|
|
|
|
) or astroid_utils.is_constructor(node.scope()):
|
|
|
|
type_ = "attribute"
|
|
|
|
|
|
|
|
assign_value = astroid_utils.get_assign_value(node)
|
|
|
|
if not assign_value:
|
|
|
|
return []
|
|
|
|
|
2019-03-30 20:43:26 +00:00
|
|
|
target = assign_value[0]
|
|
|
|
value = None
|
|
|
|
try:
|
2019-10-05 22:11:23 +00:00
|
|
|
value = self._decode(assign_value[1])
|
2019-03-30 20:43:26 +00:00
|
|
|
except UnicodeDecodeError:
|
|
|
|
# Ignore binary data on Python 2.7
|
|
|
|
if sys.version_info[0] >= 3:
|
|
|
|
raise
|
|
|
|
|
2019-04-22 04:45:08 +00:00
|
|
|
annotation = astroid_utils.get_assign_annotation(node)
|
|
|
|
|
2019-03-06 06:27:57 +00:00
|
|
|
data = {
|
|
|
|
"type": type_,
|
|
|
|
"name": target,
|
|
|
|
"full_name": self._get_full_name(target),
|
2019-10-05 22:11:23 +00:00
|
|
|
"doc": self._decode(doc),
|
2019-03-06 06:27:57 +00:00
|
|
|
"value": value,
|
|
|
|
"from_line_no": node.fromlineno,
|
|
|
|
"to_line_no": node.tolineno,
|
2019-04-22 04:45:08 +00:00
|
|
|
"annotation": annotation,
|
2019-03-06 06:27:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return [data]
|
|
|
|
|
|
|
|
def parse_classdef(self, node, data=None):
|
|
|
|
type_ = "class"
|
|
|
|
if astroid_utils.is_exception(node):
|
|
|
|
type_ = "exception"
|
|
|
|
|
|
|
|
args = ""
|
|
|
|
try:
|
|
|
|
constructor = node.lookup("__init__")[1]
|
|
|
|
except IndexError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
if isinstance(constructor, astroid.nodes.FunctionDef):
|
|
|
|
args = constructor.args.as_string()
|
|
|
|
|
|
|
|
basenames = list(astroid_utils.get_full_basenames(node.bases, node.basenames))
|
|
|
|
|
|
|
|
data = {
|
|
|
|
"type": type_,
|
|
|
|
"name": node.name,
|
|
|
|
"full_name": self._get_full_name(node.name),
|
|
|
|
"args": args,
|
|
|
|
"bases": basenames,
|
2019-10-05 22:11:23 +00:00
|
|
|
"doc": self._decode(node.doc or ""),
|
2019-03-06 06:27:57 +00:00
|
|
|
"from_line_no": node.fromlineno,
|
|
|
|
"to_line_no": node.tolineno,
|
|
|
|
"children": [],
|
|
|
|
}
|
|
|
|
|
|
|
|
self._name_stack.append(node.name)
|
|
|
|
for child in node.get_children():
|
|
|
|
child_data = self.parse(child)
|
|
|
|
if child_data:
|
|
|
|
data["children"].extend(child_data)
|
|
|
|
self._name_stack.pop()
|
|
|
|
|
|
|
|
return [data]
|
|
|
|
|
2019-07-18 03:58:00 +00:00
|
|
|
def parse_asyncfunctiondef(self, node):
|
|
|
|
return self.parse_functiondef(node)
|
2019-03-06 06:27:57 +00:00
|
|
|
|
2019-10-05 22:11:23 +00:00
|
|
|
def parse_functiondef(self, node): # pylint: disable=too-many-branches
|
2019-03-06 06:27:57 +00:00
|
|
|
if astroid_utils.is_decorated_with_property_setter(node):
|
|
|
|
return []
|
|
|
|
|
2019-07-18 03:58:00 +00:00
|
|
|
type_ = "method"
|
|
|
|
properties = []
|
|
|
|
if node.type == "function":
|
|
|
|
type_ = "function"
|
|
|
|
elif astroid_utils.is_decorated_with_property(node):
|
|
|
|
type_ = "property"
|
|
|
|
properties.append("property")
|
|
|
|
else:
|
2019-09-04 21:44:35 +00:00
|
|
|
# "__new__" method is implicit classmethod
|
|
|
|
if node.type in ("staticmethod", "classmethod") and node.name != "__new__":
|
2019-07-18 03:58:00 +00:00
|
|
|
properties.append(node.type)
|
|
|
|
if node.is_abstract(pass_is_abstract=False):
|
|
|
|
properties.append("abstractmethod")
|
|
|
|
|
|
|
|
if isinstance(node, astroid.AsyncFunctionDef):
|
|
|
|
properties.append("async")
|
2019-03-06 06:27:57 +00:00
|
|
|
|
2019-04-22 04:45:08 +00:00
|
|
|
return_annotation = None
|
|
|
|
if node.returns:
|
|
|
|
return_annotation = node.returns.as_string()
|
|
|
|
# Python 2 has no support for type annotations, so use getattr
|
|
|
|
elif getattr(node, "type_comment_returns", None):
|
|
|
|
return_annotation = node.type_comment_returns.as_string()
|
|
|
|
|
2019-08-08 06:23:21 +00:00
|
|
|
arg_string = astroid_utils.format_args(node.args)
|
|
|
|
|
2019-03-06 06:27:57 +00:00
|
|
|
data = {
|
|
|
|
"type": type_,
|
|
|
|
"name": node.name,
|
|
|
|
"full_name": self._get_full_name(node.name),
|
2019-08-08 06:23:21 +00:00
|
|
|
"args": arg_string,
|
2019-10-05 22:11:23 +00:00
|
|
|
"doc": self._decode(node.doc or ""),
|
2019-03-06 06:27:57 +00:00
|
|
|
"from_line_no": node.fromlineno,
|
|
|
|
"to_line_no": node.tolineno,
|
2019-04-22 04:45:08 +00:00
|
|
|
"return_annotation": return_annotation,
|
2019-07-18 03:58:00 +00:00
|
|
|
"properties": properties,
|
2019-03-06 06:27:57 +00:00
|
|
|
}
|
|
|
|
|
2019-07-18 03:58:00 +00:00
|
|
|
if type_ in ("method", "property"):
|
2019-03-06 06:27:57 +00:00
|
|
|
data["method_type"] = node.type
|
|
|
|
|
|
|
|
result = [data]
|
|
|
|
|
|
|
|
if node.name == "__init__":
|
|
|
|
for child in node.get_children():
|
2019-04-22 04:45:08 +00:00
|
|
|
if isinstance(child, (astroid.nodes.Assign, astroid.nodes.AnnAssign)):
|
2019-03-06 06:27:57 +00:00
|
|
|
child_data = self.parse_assign(child)
|
|
|
|
result.extend(data for data in child_data if data["doc"])
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
def _parse_local_import_from(self, node):
|
|
|
|
result = []
|
|
|
|
|
|
|
|
for name, alias in node.names:
|
|
|
|
is_wildcard = (alias or name) == "*"
|
|
|
|
full_name = self._get_full_name(alias or name)
|
|
|
|
original_path = astroid_utils.get_full_import_name(node, alias or name)
|
|
|
|
|
|
|
|
data = {
|
|
|
|
"type": "placeholder",
|
|
|
|
"name": original_path if is_wildcard else (alias or name),
|
|
|
|
"full_name": full_name,
|
|
|
|
"original_path": original_path,
|
|
|
|
}
|
|
|
|
result.append(data)
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
def parse_module(self, node):
|
|
|
|
path = node.path
|
|
|
|
if isinstance(node.path, list):
|
|
|
|
path = node.path[0] if node.path else None
|
|
|
|
|
|
|
|
type_ = "module"
|
|
|
|
if node.package:
|
|
|
|
type_ = "package"
|
|
|
|
|
|
|
|
self._name_stack = [node.name]
|
|
|
|
self._encoding = node.file_encoding
|
|
|
|
|
|
|
|
data = {
|
|
|
|
"type": type_,
|
|
|
|
"name": node.name,
|
|
|
|
"full_name": node.name,
|
2019-10-05 22:11:23 +00:00
|
|
|
"doc": self._decode(node.doc or ""),
|
2019-03-06 06:27:57 +00:00
|
|
|
"children": [],
|
|
|
|
"file_path": path,
|
|
|
|
"encoding": node.file_encoding,
|
|
|
|
"all": astroid_utils.get_module_all(node),
|
|
|
|
}
|
|
|
|
|
|
|
|
top_name = node.name.split(".", 1)[0]
|
|
|
|
for child in node.get_children():
|
2019-06-23 19:36:58 +00:00
|
|
|
if astroid_utils.is_local_import_from(child, top_name):
|
2019-03-06 06:27:57 +00:00
|
|
|
child_data = self._parse_local_import_from(child)
|
|
|
|
else:
|
|
|
|
child_data = self.parse(child)
|
|
|
|
|
|
|
|
if child_data:
|
|
|
|
data["children"].extend(child_data)
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
def parse(self, node):
|
|
|
|
data = {}
|
|
|
|
|
|
|
|
node_type = node.__class__.__name__.lower()
|
|
|
|
parse_func = getattr(self, "parse_" + node_type, None)
|
|
|
|
if parse_func:
|
|
|
|
data = parse_func(node)
|
|
|
|
else:
|
|
|
|
for child in node.get_children():
|
|
|
|
data = self.parse(child)
|
|
|
|
if data:
|
|
|
|
break
|
|
|
|
|
|
|
|
return data
|