You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
7.8 KiB
219 lines
7.8 KiB
6 years ago
|
"""
|
||
|
Module for statical analysis.
|
||
|
"""
|
||
|
from parso.python import tree
|
||
|
|
||
|
from jedi._compatibility import force_unicode
|
||
|
from jedi import debug
|
||
|
from jedi.evaluate.compiled import CompiledObject
|
||
|
from jedi.evaluate.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(object):
|
||
|
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 __unicode__(self):
|
||
|
return '%s:%s:%s: %s %s' % (self.path, self.line, self.column,
|
||
|
self.code, self.message)
|
||
|
|
||
|
def __str__(self):
|
||
|
return self.__unicode__()
|
||
|
|
||
|
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__()
|
||
|
instance = typ(error_name, module_path, node.start_pos, message)
|
||
|
debug.warning(str(instance), format=False)
|
||
|
node_context.evaluator.analysis.append(instance)
|
||
|
|
||
|
|
||
|
def _check_for_setattr(instance):
|
||
|
"""
|
||
|
Check if there's any setattr method inside an instance. If so, return True.
|
||
|
"""
|
||
|
from jedi.evaluate.context import ModuleContext
|
||
|
module = instance.get_root_context()
|
||
|
if not isinstance(module, ModuleContext):
|
||
|
return False
|
||
|
|
||
|
node = module.tree_node
|
||
|
try:
|
||
|
stmts = node.get_used_names()['setattr']
|
||
|
except KeyError:
|
||
|
return False
|
||
|
|
||
|
return any(node.start_pos < stmt.start_pos < node.end_pos
|
||
|
for stmt in stmts)
|
||
|
|
||
|
|
||
|
def add_attribute_error(name_context, lookup_context, name):
|
||
|
message = ('AttributeError: %s has no attribute %s.' % (lookup_context, name))
|
||
|
from jedi.evaluate.context.instance import AbstractInstanceContext, CompiledInstanceName
|
||
|
# Check for __getattr__/__getattribute__ existance and issue a warning
|
||
|
# instead of an error, if that happens.
|
||
|
typ = Error
|
||
|
if isinstance(lookup_context, AbstractInstanceContext):
|
||
|
slot_names = lookup_context.get_function_slot_names(u'__getattr__') + \
|
||
|
lookup_context.get_function_slot_names(u'__getattribute__')
|
||
|
for n in slot_names:
|
||
|
# TODO do we even get here?
|
||
|
if isinstance(name, CompiledInstanceName) and \
|
||
|
n.parent_context.obj == object:
|
||
|
typ = Warning
|
||
|
break
|
||
|
|
||
|
if _check_for_setattr(lookup_context):
|
||
|
typ = Warning
|
||
|
|
||
|
payload = lookup_context, 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):
|
||
|
try:
|
||
|
return isinstance(cls, CompiledObject) and cls.is_super_class(exception)
|
||
|
except TypeError:
|
||
|
return False
|
||
|
|
||
|
def check_try_for_except(obj, exception):
|
||
|
# Only nodes in try
|
||
|
iterator = iter(obj.children)
|
||
|
for branch_type in iterator:
|
||
|
colon = next(iterator)
|
||
|
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.eval_node(node)
|
||
|
for cls in except_classes:
|
||
|
from jedi.evaluate.context import iterable
|
||
|
if isinstance(cls, iterable.Sequence) and \
|
||
|
cls.array_type == 'tuple':
|
||
|
# multiple exceptions
|
||
|
for lazy_context in cls.py__iter__():
|
||
|
for typ in lazy_context.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.evaluate.arguments import TreeArguments
|
||
|
args = list(TreeArguments(node_context.evaluator, node_context, arglist).unpack())
|
||
|
# Arguments should be very simple
|
||
|
assert len(args) == 2
|
||
|
|
||
|
# Check name
|
||
|
key, lazy_context = args[1]
|
||
|
names = list(lazy_context.infer())
|
||
|
assert len(names) == 1 and is_string(names[0])
|
||
|
assert force_unicode(names[0].get_safe_value()) == payload[1].value
|
||
|
|
||
|
# Check objects
|
||
|
key, lazy_context = args[0]
|
||
|
objects = lazy_context.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
|