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