analysis.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. """
  2. Module for statical analysis.
  3. """
  4. from parso.python import tree
  5. from jedi import debug
  6. from jedi.inference.helpers import is_string
  7. CODES = {
  8. 'attribute-error': (1, AttributeError, 'Potential AttributeError.'),
  9. 'name-error': (2, NameError, 'Potential NameError.'),
  10. 'import-error': (3, ImportError, 'Potential ImportError.'),
  11. 'type-error-too-many-arguments': (4, TypeError, None),
  12. 'type-error-too-few-arguments': (5, TypeError, None),
  13. 'type-error-keyword-argument': (6, TypeError, None),
  14. 'type-error-multiple-values': (7, TypeError, None),
  15. 'type-error-star-star': (8, TypeError, None),
  16. 'type-error-star': (9, TypeError, None),
  17. 'type-error-operation': (10, TypeError, None),
  18. 'type-error-not-iterable': (11, TypeError, None),
  19. 'type-error-isinstance': (12, TypeError, None),
  20. 'type-error-not-subscriptable': (13, TypeError, None),
  21. 'value-error-too-many-values': (14, ValueError, None),
  22. 'value-error-too-few-values': (15, ValueError, None),
  23. }
  24. class Error:
  25. def __init__(self, name, module_path, start_pos, message=None):
  26. self.path = module_path
  27. self._start_pos = start_pos
  28. self.name = name
  29. if message is None:
  30. message = CODES[self.name][2]
  31. self.message = message
  32. @property
  33. def line(self):
  34. return self._start_pos[0]
  35. @property
  36. def column(self):
  37. return self._start_pos[1]
  38. @property
  39. def code(self):
  40. # The class name start
  41. first = self.__class__.__name__[0]
  42. return first + str(CODES[self.name][0])
  43. def __str__(self):
  44. return '%s:%s:%s: %s %s' % (self.path, self.line, self.column,
  45. self.code, self.message)
  46. def __eq__(self, other):
  47. return (self.path == other.path and self.name == other.name
  48. and self._start_pos == other._start_pos)
  49. def __ne__(self, other):
  50. return not self.__eq__(other)
  51. def __hash__(self):
  52. return hash((self.path, self._start_pos, self.name))
  53. def __repr__(self):
  54. return '<%s %s: %s@%s,%s>' % (self.__class__.__name__,
  55. self.name, self.path,
  56. self._start_pos[0], self._start_pos[1])
  57. class Warning(Error):
  58. pass
  59. def add(node_context, error_name, node, message=None, typ=Error, payload=None):
  60. exception = CODES[error_name][1]
  61. if _check_for_exception_catch(node_context, node, exception, payload):
  62. return
  63. # TODO this path is probably not right
  64. module_context = node_context.get_root_context()
  65. module_path = module_context.py__file__()
  66. issue_instance = typ(error_name, module_path, node.start_pos, message)
  67. debug.warning(str(issue_instance), format=False)
  68. node_context.inference_state.analysis.append(issue_instance)
  69. return issue_instance
  70. def _check_for_setattr(instance):
  71. """
  72. Check if there's any setattr method inside an instance. If so, return True.
  73. """
  74. module = instance.get_root_context()
  75. node = module.tree_node
  76. if node is None:
  77. # If it's a compiled module or doesn't have a tree_node
  78. return False
  79. try:
  80. stmt_names = node.get_used_names()['setattr']
  81. except KeyError:
  82. return False
  83. return any(node.start_pos < n.start_pos < node.end_pos
  84. # Check if it's a function called setattr.
  85. and not (n.parent.type == 'funcdef' and n.parent.name == n)
  86. for n in stmt_names)
  87. def add_attribute_error(name_context, lookup_value, name):
  88. message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name))
  89. # Check for __getattr__/__getattribute__ existance and issue a warning
  90. # instead of an error, if that happens.
  91. typ = Error
  92. if lookup_value.is_instance() and not lookup_value.is_compiled():
  93. # TODO maybe make a warning for __getattr__/__getattribute__
  94. if _check_for_setattr(lookup_value):
  95. typ = Warning
  96. payload = lookup_value, name
  97. add(name_context, 'attribute-error', name, message, typ, payload)
  98. def _check_for_exception_catch(node_context, jedi_name, exception, payload=None):
  99. """
  100. Checks if a jedi object (e.g. `Statement`) sits inside a try/catch and
  101. doesn't count as an error (if equal to `exception`).
  102. Also checks `hasattr` for AttributeErrors and uses the `payload` to compare
  103. it.
  104. Returns True if the exception was catched.
  105. """
  106. def check_match(cls, exception):
  107. if not cls.is_class():
  108. return False
  109. for python_cls in exception.mro():
  110. if cls.py__name__() == python_cls.__name__ \
  111. and cls.parent_context.is_builtins_module():
  112. return True
  113. return False
  114. def check_try_for_except(obj, exception):
  115. # Only nodes in try
  116. iterator = iter(obj.children)
  117. for branch_type in iterator:
  118. next(iterator) # The colon
  119. suite = next(iterator)
  120. if branch_type == 'try' \
  121. and not (branch_type.start_pos < jedi_name.start_pos <= suite.end_pos):
  122. return False
  123. for node in obj.get_except_clause_tests():
  124. if node is None:
  125. return True # An exception block that catches everything.
  126. else:
  127. except_classes = node_context.infer_node(node)
  128. for cls in except_classes:
  129. from jedi.inference.value import iterable
  130. if isinstance(cls, iterable.Sequence) and \
  131. cls.array_type == 'tuple':
  132. # multiple exceptions
  133. for lazy_value in cls.py__iter__():
  134. for typ in lazy_value.infer():
  135. if check_match(typ, exception):
  136. return True
  137. else:
  138. if check_match(cls, exception):
  139. return True
  140. def check_hasattr(node, suite):
  141. try:
  142. assert suite.start_pos <= jedi_name.start_pos < suite.end_pos
  143. assert node.type in ('power', 'atom_expr')
  144. base = node.children[0]
  145. assert base.type == 'name' and base.value == 'hasattr'
  146. trailer = node.children[1]
  147. assert trailer.type == 'trailer'
  148. arglist = trailer.children[1]
  149. assert arglist.type == 'arglist'
  150. from jedi.inference.arguments import TreeArguments
  151. args = TreeArguments(node_context.inference_state, node_context, arglist)
  152. unpacked_args = list(args.unpack())
  153. # Arguments should be very simple
  154. assert len(unpacked_args) == 2
  155. # Check name
  156. key, lazy_value = unpacked_args[1]
  157. names = list(lazy_value.infer())
  158. assert len(names) == 1 and is_string(names[0])
  159. assert names[0].get_safe_value() == payload[1].value
  160. # Check objects
  161. key, lazy_value = unpacked_args[0]
  162. objects = lazy_value.infer()
  163. return payload[0] in objects
  164. except AssertionError:
  165. return False
  166. obj = jedi_name
  167. while obj is not None and not isinstance(obj, (tree.Function, tree.Class)):
  168. if isinstance(obj, tree.Flow):
  169. # try/except catch check
  170. if obj.type == 'try_stmt' and check_try_for_except(obj, exception):
  171. return True
  172. # hasattr check
  173. if exception == AttributeError and obj.type in ('if_stmt', 'while_stmt'):
  174. if check_hasattr(obj.children[1], obj.children[3]):
  175. return True
  176. obj = obj.parent
  177. return False