PageRenderTime 44ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/ftplugin/python/logilab/astng/builder.py

https://github.com/vim-scripts/pylint-mode
Python | 231 lines | 180 code | 9 blank | 42 comment | 19 complexity | cfd72e96019aad2c00cd1dc8370dc670 MD5 | raw file
  1. # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  2. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
  3. # copyright 2003-2010 Sylvain Thenault, all rights reserved.
  4. # contact mailto:thenault@gmail.com
  5. #
  6. # This file is part of logilab-astng.
  7. #
  8. # logilab-astng is free software: you can redistribute it and/or modify it
  9. # under the terms of the GNU Lesser General Public License as published by the
  10. # Free Software Foundation, either version 2.1 of the License, or (at your
  11. # option) any later version.
  12. #
  13. # logilab-astng is distributed in the hope that it will be useful, but
  14. # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
  16. # for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public License along
  19. # with logilab-astng. If not, see <http://www.gnu.org/licenses/>.
  20. """The ASTNGBuilder makes astng from living object and / or from compiler.ast
  21. With python >= 2.5, the internal _ast module is used instead
  22. The builder is not thread safe and can't be used to parse different sources
  23. at the same time.
  24. """
  25. __docformat__ = "restructuredtext en"
  26. import sys, re
  27. from os.path import splitext, basename, dirname, exists, abspath
  28. from logilab.common.modutils import modpath_from_file
  29. from logilab.astng.exceptions import ASTNGBuildingException, InferenceError
  30. from logilab.astng.raw_building import InspectBuilder
  31. from logilab.astng.rebuilder import TreeRebuilder
  32. from logilab.astng.manager import ASTNGManager
  33. from logilab.astng.bases import YES, Instance
  34. from _ast import PyCF_ONLY_AST
  35. def parse(string):
  36. return compile(string, "<string>", 'exec', PyCF_ONLY_AST)
  37. if sys.version_info >= (3, 0):
  38. from tokenize import detect_encoding
  39. def open_source_file(filename):
  40. byte_stream = open(filename, 'bU')
  41. encoding = detect_encoding(byte_stream.readline)[0]
  42. stream = open(filename, 'U', encoding=encoding)
  43. try:
  44. data = stream.read()
  45. except UnicodeError, uex: # wrong encodingg
  46. # detect_encoding returns utf-8 if no encoding specified
  47. msg = 'Wrong (%s) or no encoding specified' % encoding
  48. raise ASTNGBuildingException(msg)
  49. return stream, encoding, data
  50. else:
  51. import re
  52. _ENCODING_RGX = re.compile("\s*#+.*coding[:=]\s*([-\w.]+)")
  53. def _guess_encoding(string):
  54. """get encoding from a python file as string or return None if not found
  55. """
  56. # check for UTF-8 byte-order mark
  57. if string.startswith('\xef\xbb\xbf'):
  58. return 'UTF-8'
  59. for line in string.split('\n', 2)[:2]:
  60. # check for encoding declaration
  61. match = _ENCODING_RGX.match(line)
  62. if match is not None:
  63. return match.group(1)
  64. def open_source_file(filename):
  65. """get data for parsing a file"""
  66. stream = open(filename, 'U')
  67. data = stream.read()
  68. encoding = _guess_encoding(data)
  69. return stream, encoding, data
  70. # ast NG builder ##############################################################
  71. MANAGER = ASTNGManager()
  72. class ASTNGBuilder(InspectBuilder):
  73. """provide astng building methods"""
  74. rebuilder = TreeRebuilder()
  75. def __init__(self, manager=None):
  76. self._manager = manager or MANAGER
  77. def module_build(self, module, modname=None):
  78. """build an astng from a living module instance
  79. """
  80. node = None
  81. path = getattr(module, '__file__', None)
  82. if path is not None:
  83. path_, ext = splitext(module.__file__)
  84. if ext in ('.py', '.pyc', '.pyo') and exists(path_ + '.py'):
  85. node = self.file_build(path_ + '.py', modname)
  86. if node is None:
  87. # this is a built-in module
  88. # get a partial representation by introspection
  89. node = self.inspect_build(module, modname=modname, path=path)
  90. return node
  91. def file_build(self, path, modname=None):
  92. """build astng from a source code file (i.e. from an ast)
  93. path is expected to be a python source file
  94. """
  95. try:
  96. stream, encoding, data = open_source_file(path)
  97. except IOError, exc:
  98. msg = 'Unable to load file %r (%s)' % (path, exc)
  99. raise ASTNGBuildingException(msg)
  100. except SyntaxError, exc: # py3k encoding specification error
  101. raise ASTNGBuildingException(exc)
  102. except LookupError, exc: # unknown encoding
  103. raise ASTNGBuildingException(exc)
  104. # get module name if necessary, *before modifying sys.path*
  105. if modname is None:
  106. try:
  107. modname = '.'.join(modpath_from_file(path))
  108. except ImportError:
  109. modname = splitext(basename(path))[0]
  110. # build astng representation
  111. try:
  112. sys.path.insert(0, dirname(path)) # XXX (syt) iirk
  113. node = self.string_build(data, modname, path)
  114. finally:
  115. sys.path.pop(0)
  116. node.file_encoding = encoding
  117. node.file_stream = stream
  118. return node
  119. def string_build(self, data, modname='', path=None):
  120. """build astng from source code string and return rebuilded astng"""
  121. module = self._data_build(data, modname, path)
  122. if self._manager is not None:
  123. self._manager.astng_cache[module.name] = module
  124. # post tree building steps after we stored the module in the cache:
  125. for from_node in module._from_nodes:
  126. self.add_from_names_to_locals(from_node)
  127. # handle delayed assattr nodes
  128. for delayed in module._delayed_assattr:
  129. self.delayed_assattr(delayed)
  130. return module
  131. def _data_build(self, data, modname, path):
  132. """build tree node from data and add some informations"""
  133. # this method could be wrapped with a pickle/cache function
  134. node = parse(data + '\n')
  135. if path is not None:
  136. node_file = abspath(path)
  137. else:
  138. node_file = '<?>'
  139. if modname.endswith('.__init__'):
  140. modname = modname[:-9]
  141. package = True
  142. else:
  143. package = path and path.find('__init__.py') > -1 or False
  144. self.rebuilder.init()
  145. module = self.rebuilder.visit_module(node, modname, package)
  146. module.file = module.path = node_file
  147. module._from_nodes = self.rebuilder._from_nodes
  148. module._delayed_assattr = self.rebuilder._delayed_assattr
  149. return module
  150. def add_from_names_to_locals(self, node):
  151. """store imported names to the locals;
  152. resort the locals if coming from a delayed node
  153. """
  154. _key_func = lambda node: node.fromlineno
  155. def sort_locals(my_list):
  156. my_list.sort(key=_key_func)
  157. for (name, asname) in node.names:
  158. if name == '*':
  159. try:
  160. imported = node.root().import_module(node.modname)
  161. except ASTNGBuildingException:
  162. continue
  163. for name in imported.wildcard_import_names():
  164. node.parent.set_local(name, node)
  165. sort_locals(node.parent.scope().locals[name])
  166. else:
  167. node.parent.set_local(asname or name, node)
  168. sort_locals(node.parent.scope().locals[asname or name])
  169. def delayed_assattr(self, node):
  170. """visit a AssAttr node -> add name to locals, handle members
  171. definition
  172. """
  173. try:
  174. frame = node.frame()
  175. for infered in node.expr.infer():
  176. if infered is YES:
  177. continue
  178. try:
  179. if infered.__class__ is Instance:
  180. infered = infered._proxied
  181. iattrs = infered.instance_attrs
  182. elif isinstance(infered, Instance):
  183. # Const, Tuple, ... we may be wrong, may be not, but
  184. # anyway we don't want to pollute builtin's namespace
  185. continue
  186. elif infered.is_function:
  187. iattrs = infered.instance_attrs
  188. else:
  189. iattrs = infered.locals
  190. except AttributeError:
  191. # XXX log error
  192. #import traceback
  193. #traceback.print_exc()
  194. continue
  195. values = iattrs.setdefault(node.attrname, [])
  196. if node in values:
  197. continue
  198. # get assign in __init__ first XXX useful ?
  199. if frame.name == '__init__' and values and not \
  200. values[0].frame().name == '__init__':
  201. values.insert(0, node)
  202. else:
  203. values.append(node)
  204. except InferenceError:
  205. pass