| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- """
- Module for statical analysis.
- """
- from parso.python import tree
- from jedi import debug
- from jedi.inference.helpers import is_string
- CODES = {
- 'attribute-error': (1, AttributeError, 'Potential AttributeError.'),
- 'name-error': (2, NameError, 'Potential NameError.'),
- 'import-error': (3, ImportError, 'Potential ImportError.'),
- 'type-error-too-many-arguments': (4, TypeError, None),
- 'type-error-too-few-arguments': (5, TypeError, None),
- 'type-error-keyword-argument': (6, TypeError, None),
- 'type-error-multiple-values': (7, TypeError, None),
- 'type-error-star-star': (8, TypeError, None),
- 'type-error-star': (9, TypeError, None),
- 'type-error-operation': (10, TypeError, None),
- 'type-error-not-iterable': (11, TypeError, None),
- 'type-error-isinstance': (12, TypeError, None),
- 'type-error-not-subscriptable': (13, TypeError, None),
- 'value-error-too-many-values': (14, ValueError, None),
- 'value-error-too-few-values': (15, ValueError, None),
- }
- class Error:
- def __init__(self, name, module_path, start_pos, message=None):
- self.path = module_path
- self._start_pos = start_pos
- self.name = name
- if message is None:
- message = CODES[self.name][2]
- self.message = message
- @property
- def line(self):
- return self._start_pos[0]
- @property
- def column(self):
- return self._start_pos[1]
- @property
- def code(self):
- # The class name start
- first = self.__class__.__name__[0]
- return first + str(CODES[self.name][0])
- def __str__(self):
- return '%s:%s:%s: %s %s' % (self.path, self.line, self.column,
- self.code, self.message)
- def __eq__(self, other):
- return (self.path == other.path and self.name == other.name
- and self._start_pos == other._start_pos)
- def __ne__(self, other):
- return not self.__eq__(other)
- def __hash__(self):
- return hash((self.path, self._start_pos, self.name))
- def __repr__(self):
- return '<%s %s: %s@%s,%s>' % (self.__class__.__name__,
- self.name, self.path,
- self._start_pos[0], self._start_pos[1])
- class Warning(Error):
- pass
- def add(node_context, error_name, node, message=None, typ=Error, payload=None):
- exception = CODES[error_name][1]
- if _check_for_exception_catch(node_context, node, exception, payload):
- return
- # TODO this path is probably not right
- module_context = node_context.get_root_context()
- module_path = module_context.py__file__()
- issue_instance = typ(error_name, module_path, node.start_pos, message)
- debug.warning(str(issue_instance), format=False)
- node_context.inference_state.analysis.append(issue_instance)
- return issue_instance
- def _check_for_setattr(instance):
- """
- Check if there's any setattr method inside an instance. If so, return True.
- """
- module = instance.get_root_context()
- node = module.tree_node
- if node is None:
- # If it's a compiled module or doesn't have a tree_node
- return False
- try:
- stmt_names = node.get_used_names()['setattr']
- except KeyError:
- return False
- return any(node.start_pos < n.start_pos < node.end_pos
- # Check if it's a function called setattr.
- and not (n.parent.type == 'funcdef' and n.parent.name == n)
- for n in stmt_names)
- def add_attribute_error(name_context, lookup_value, name):
- message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name))
- # Check for __getattr__/__getattribute__ existance and issue a warning
- # instead of an error, if that happens.
- typ = Error
- if lookup_value.is_instance() and not lookup_value.is_compiled():
- # TODO maybe make a warning for __getattr__/__getattribute__
- if _check_for_setattr(lookup_value):
- typ = Warning
- payload = lookup_value, name
- add(name_context, 'attribute-error', name, message, typ, payload)
- def _check_for_exception_catch(node_context, jedi_name, exception, payload=None):
- """
- Checks if a jedi object (e.g. `Statement`) sits inside a try/catch and
- doesn't count as an error (if equal to `exception`).
- Also checks `hasattr` for AttributeErrors and uses the `payload` to compare
- it.
- Returns True if the exception was catched.
- """
- def check_match(cls, exception):
- if not cls.is_class():
- return False
- for python_cls in exception.mro():
- if cls.py__name__() == python_cls.__name__ \
- and cls.parent_context.is_builtins_module():
- return True
- return False
- def check_try_for_except(obj, exception):
- # Only nodes in try
- iterator = iter(obj.children)
- for branch_type in iterator:
- next(iterator) # The colon
- suite = next(iterator)
- if branch_type == 'try' \
- and not (branch_type.start_pos < jedi_name.start_pos <= suite.end_pos):
- return False
- for node in obj.get_except_clause_tests():
- if node is None:
- return True # An exception block that catches everything.
- else:
- except_classes = node_context.infer_node(node)
- for cls in except_classes:
- from jedi.inference.value import iterable
- if isinstance(cls, iterable.Sequence) and \
- cls.array_type == 'tuple':
- # multiple exceptions
- for lazy_value in cls.py__iter__():
- for typ in lazy_value.infer():
- if check_match(typ, exception):
- return True
- else:
- if check_match(cls, exception):
- return True
- def check_hasattr(node, suite):
- try:
- assert suite.start_pos <= jedi_name.start_pos < suite.end_pos
- assert node.type in ('power', 'atom_expr')
- base = node.children[0]
- assert base.type == 'name' and base.value == 'hasattr'
- trailer = node.children[1]
- assert trailer.type == 'trailer'
- arglist = trailer.children[1]
- assert arglist.type == 'arglist'
- from jedi.inference.arguments import TreeArguments
- args = TreeArguments(node_context.inference_state, node_context, arglist)
- unpacked_args = list(args.unpack())
- # Arguments should be very simple
- assert len(unpacked_args) == 2
- # Check name
- key, lazy_value = unpacked_args[1]
- names = list(lazy_value.infer())
- assert len(names) == 1 and is_string(names[0])
- assert names[0].get_safe_value() == payload[1].value
- # Check objects
- key, lazy_value = unpacked_args[0]
- objects = lazy_value.infer()
- return payload[0] in objects
- except AssertionError:
- return False
- obj = jedi_name
- while obj is not None and not isinstance(obj, (tree.Function, tree.Class)):
- if isinstance(obj, tree.Flow):
- # try/except catch check
- if obj.type == 'try_stmt' and check_try_for_except(obj, exception):
- return True
- # hasattr check
- if exception == AttributeError and obj.type in ('if_stmt', 'while_stmt'):
- if check_hasattr(obj.children[1], obj.children[3]):
- return True
- obj = obj.parent
- return False
|