inspector.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. # This program is free software; you can redistribute it and/or modify it under
  2. # the terms of the GNU Lesser General Public License as published by the Free Software
  3. # Foundation; either version 2 of the License, or (at your option) any later
  4. # version.
  5. #
  6. # This program is distributed in the hope that it will be useful, but WITHOUT
  7. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  8. # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
  9. #
  10. # You should have received a copy of the GNU Lesser General Public License along with
  11. # this program; if not, write to the Free Software Foundation, Inc.,
  12. # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  13. # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  14. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
  15. # copyright 2003-2010 Sylvain Thenault, all rights reserved.
  16. # contact mailto:thenault@gmail.com
  17. #
  18. # This file is part of logilab-astng.
  19. #
  20. # logilab-astng is free software: you can redistribute it and/or modify it
  21. # under the terms of the GNU Lesser General Public License as published by the
  22. # Free Software Foundation, either version 2.1 of the License, or (at your
  23. # option) any later version.
  24. #
  25. # logilab-astng is distributed in the hope that it will be useful, but
  26. # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  27. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
  28. # for more details.
  29. #
  30. # You should have received a copy of the GNU Lesser General Public License along
  31. # with logilab-astng. If not, see <http://www.gnu.org/licenses/>.
  32. """visitor doing some postprocessing on the astng tree.
  33. Try to resolve definitions (namespace) dictionary, relationship...
  34. This module has been imported from pyreverse
  35. """
  36. __docformat__ = "restructuredtext en"
  37. from os.path import dirname
  38. from logilab.common.modutils import get_module_part, is_relative, \
  39. is_standard_module
  40. from logilab import astng
  41. from logilab.astng.exceptions import InferenceError
  42. from logilab.astng.utils import LocalsVisitor
  43. class IdGeneratorMixIn:
  44. """
  45. Mixin adding the ability to generate integer uid
  46. """
  47. def __init__(self, start_value=0):
  48. self.id_count = start_value
  49. def init_counter(self, start_value=0):
  50. """init the id counter
  51. """
  52. self.id_count = start_value
  53. def generate_id(self):
  54. """generate a new identifier
  55. """
  56. self.id_count += 1
  57. return self.id_count
  58. class Linker(IdGeneratorMixIn, LocalsVisitor):
  59. """
  60. walk on the project tree and resolve relationships.
  61. According to options the following attributes may be added to visited nodes:
  62. * uid,
  63. a unique identifier for the node (on astng.Project, astng.Module,
  64. astng.Class and astng.locals_type). Only if the linker has been instantiated
  65. with tag=True parameter (False by default).
  66. * Function
  67. a mapping from locals names to their bounded value, which may be a
  68. constant like a string or an integer, or an astng node (on astng.Module,
  69. astng.Class and astng.Function).
  70. * instance_attrs_type
  71. as locals_type but for klass member attributes (only on astng.Class)
  72. * implements,
  73. list of implemented interface _objects_ (only on astng.Class nodes)
  74. """
  75. def __init__(self, project, inherited_interfaces=0, tag=False):
  76. IdGeneratorMixIn.__init__(self)
  77. LocalsVisitor.__init__(self)
  78. # take inherited interface in consideration or not
  79. self.inherited_interfaces = inherited_interfaces
  80. # tag nodes or not
  81. self.tag = tag
  82. # visited project
  83. self.project = project
  84. def visit_project(self, node):
  85. """visit an astng.Project node
  86. * optionally tag the node with a unique id
  87. """
  88. if self.tag:
  89. node.uid = self.generate_id()
  90. for module in node.modules:
  91. self.visit(module)
  92. def visit_package(self, node):
  93. """visit an astng.Package node
  94. * optionally tag the node with a unique id
  95. """
  96. if self.tag:
  97. node.uid = self.generate_id()
  98. for subelmt in node.values():
  99. self.visit(subelmt)
  100. def visit_module(self, node):
  101. """visit an astng.Module node
  102. * set the locals_type mapping
  103. * set the depends mapping
  104. * optionally tag the node with a unique id
  105. """
  106. if hasattr(node, 'locals_type'):
  107. return
  108. node.locals_type = {}
  109. node.depends = []
  110. if self.tag:
  111. node.uid = self.generate_id()
  112. def visit_class(self, node):
  113. """visit an astng.Class node
  114. * set the locals_type and instance_attrs_type mappings
  115. * set the implements list and build it
  116. * optionally tag the node with a unique id
  117. """
  118. if hasattr(node, 'locals_type'):
  119. return
  120. node.locals_type = {}
  121. if self.tag:
  122. node.uid = self.generate_id()
  123. # resolve ancestors
  124. for baseobj in node.ancestors(recurs=False):
  125. specializations = getattr(baseobj, 'specializations', [])
  126. specializations.append(node)
  127. baseobj.specializations = specializations
  128. # resolve instance attributes
  129. node.instance_attrs_type = {}
  130. for assattrs in node.instance_attrs.values():
  131. for assattr in assattrs:
  132. self.handle_assattr_type(assattr, node)
  133. # resolve implemented interface
  134. try:
  135. node.implements = list(node.interfaces(self.inherited_interfaces))
  136. except InferenceError:
  137. node.implements = ()
  138. def visit_function(self, node):
  139. """visit an astng.Function node
  140. * set the locals_type mapping
  141. * optionally tag the node with a unique id
  142. """
  143. if hasattr(node, 'locals_type'):
  144. return
  145. node.locals_type = {}
  146. if self.tag:
  147. node.uid = self.generate_id()
  148. link_project = visit_project
  149. link_module = visit_module
  150. link_class = visit_class
  151. link_function = visit_function
  152. def visit_assname(self, node):
  153. """visit an astng.AssName node
  154. handle locals_type
  155. """
  156. # avoid double parsing done by different Linkers.visit
  157. # running over the same project:
  158. if hasattr(node, '_handled'):
  159. return
  160. node._handled = True
  161. if node.name in node.frame():
  162. frame = node.frame()
  163. else:
  164. # the name has been defined as 'global' in the frame and belongs
  165. # there. Btw the frame is not yet visited as the name is in the
  166. # root locals; the frame hence has no locals_type attribute
  167. frame = node.root()
  168. try:
  169. values = node.infered()
  170. try:
  171. already_infered = frame.locals_type[node.name]
  172. for valnode in values:
  173. if not valnode in already_infered:
  174. already_infered.append(valnode)
  175. except KeyError:
  176. frame.locals_type[node.name] = values
  177. except astng.InferenceError:
  178. pass
  179. def handle_assattr_type(self, node, parent):
  180. """handle an astng.AssAttr node
  181. handle instance_attrs_type
  182. """
  183. try:
  184. values = list(node.infer())
  185. try:
  186. already_infered = parent.instance_attrs_type[node.attrname]
  187. for valnode in values:
  188. if not valnode in already_infered:
  189. already_infered.append(valnode)
  190. except KeyError:
  191. parent.instance_attrs_type[node.attrname] = values
  192. except astng.InferenceError:
  193. pass
  194. def visit_import(self, node):
  195. """visit an astng.Import node
  196. resolve module dependencies
  197. """
  198. context_file = node.root().file
  199. for name in node.names:
  200. relative = is_relative(name[0], context_file)
  201. self._imported_module(node, name[0], relative)
  202. def visit_from(self, node):
  203. """visit an astng.From node
  204. resolve module dependencies
  205. """
  206. basename = node.modname
  207. context_file = node.root().file
  208. if context_file is not None:
  209. relative = is_relative(basename, context_file)
  210. else:
  211. relative = False
  212. for name in node.names:
  213. if name[0] == '*':
  214. continue
  215. # analyze dependencies
  216. fullname = '%s.%s' % (basename, name[0])
  217. if fullname.find('.') > -1:
  218. try:
  219. # XXX: don't use get_module_part, missing package precedence
  220. fullname = get_module_part(fullname)
  221. except ImportError:
  222. continue
  223. if fullname != basename:
  224. self._imported_module(node, fullname, relative)
  225. def compute_module(self, context_name, mod_path):
  226. """return true if the module should be added to dependencies"""
  227. package_dir = dirname(self.project.path)
  228. if context_name == mod_path:
  229. return 0
  230. elif is_standard_module(mod_path, (package_dir,)):
  231. return 1
  232. return 0
  233. # protected methods ########################################################
  234. def _imported_module(self, node, mod_path, relative):
  235. """notify an imported module, used to analyze dependencies
  236. """
  237. module = node.root()
  238. context_name = module.name
  239. if relative:
  240. mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]),
  241. mod_path)
  242. if self.compute_module(context_name, mod_path):
  243. # handle dependencies
  244. if not hasattr(module, 'depends'):
  245. module.depends = []
  246. mod_paths = module.depends
  247. if not mod_path in mod_paths:
  248. mod_paths.append(mod_path)