diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 48e8a56d..ce3d78e5 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -585,6 +585,19 @@ class Script(object): return [classes.CallSignature(self._evaluator, o, call, index, key_name) for o in origins if o.is_callable()] + def analysis(self): + statements = set(chain(*self._parser.module().used_names.values())) + for stmt in statements: + if isinstance(stmt, pr.Import): + imports.strip_imports(self._evaluator, [stmt]) + else: + self._evaluator.eval_statement(stmt) + + for error in self._evaluator.analysis: + print(repr(error)) + raise AssertionError + return self._evaluator.analysis + class Interpreter(Script): """ @@ -626,7 +639,7 @@ class Interpreter(Script): user_stmt = self._parser.user_stmt_with_whitespace() is_simple_path = not path or re.search('^[\w][\w\d.]*$', path) if isinstance(user_stmt, pr.Import) or not is_simple_path: - return super(type(self), self)._simple_complete(path, like) + return super(Interpreter, self)._simple_complete(path, like) else: class NamespaceModule(object): def __getattr__(_, name): @@ -638,8 +651,8 @@ class Interpreter(Script): raise AttributeError() def __dir__(_): - return list(set(chain.from_iterable(n.keys() - for n in self.namespaces))) + gen = (n.keys() for n in self.namespaces) + return list(set(chain.from_iterable(gen))) paths = path.split('.') if path else [] diff --git a/jedi/evaluate/analysis.py b/jedi/evaluate/analysis.py index 072364c6..959bba09 100644 --- a/jedi/evaluate/analysis.py +++ b/jedi/evaluate/analysis.py @@ -6,12 +6,13 @@ from jedi import debug CODES = { - 'inaccesible': (1, 'Attribute is not accessible.'), + 'attribute-error': (1, 'Potential AttributeError.'), } class Error(object): - def __init__(self, name, start_pos): + def __init__(self, name, module_path, start_pos): + self._module_path = module_path self._start_pos = start_pos self.name = name @@ -32,12 +33,18 @@ class Error(object): def __str__(self): return '%s: %s' % (self.code, self.line) + def __repr__(self): + return '<%s %s: %s@%s,%s' % (self.__class__.__name__, + self.name, self._module_path, + self._start_pos[0], self._start_pos[1]) + class Warning(Error): pass -def add(evaluator, code, typ=Error): - instance = typ() - debug.warning(str(Error)) +def add(evaluator, name, jedi_obj, typ=Error): + module_path = jedi_obj.get_parent_until().path + instance = typ(name, module_path, jedi_obj.start_pos) + debug.warning(str(instance)) evaluator.analysis.append(instance) diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index b4e2dc25..5f385657 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -24,6 +24,7 @@ from jedi.evaluate import compiled from jedi.evaluate import docstrings from jedi.evaluate import iterable from jedi.evaluate import imports +from jedi.evaluate import analysis class NameFinder(object): @@ -193,6 +194,14 @@ class NameFinder(object): # handling __getattr__ / __getattribute__ types = self._check_getattr(self.scope) + if not names and not types \ + and not (isinstance(self.name_str, pr.NamePart) + and isinstance(self.name_str.parent.parent, pr.Param)): + if not isinstance(self.name_str, (str, unicode)): # TODO Remove + analysis.add(self._evaluator, 'attribute-error', self.name_str) + print(self.scope, self.name_str.parent.parent, self.name_str, self.position, + self.name_str.start_pos) + return types def _remove_statements(self, stmt):