diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index e27b0bf7..0a819b5b 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -587,16 +587,17 @@ class Script(object): for o in origins if o.is_callable()] def _analysis(self): - statements = set(chain(*self._parser.module().used_names.values())) + #statements = set(chain(*self._parser.module().used_names.values())) + stmts, imps = analysis.get_module_statements(self._parser.module()) # Sort the statements so that the results are reproducible. - for stmt in sorted(statements, key=lambda obj: obj.start_pos): - if isinstance(stmt, pr.Import): - imps = imports.ImportWrapper(self._evaluator, stmt, - nested_resolve=True).follow() - if stmt.is_nested() and any(not isinstance(i, pr.Module) for i in imps): - analysis.add(self._evaluator, 'import-error', stmt) - elif not (isinstance(stmt.parent, pr.ForFlow) - and stmt.parent.set_stmt == stmt): + for i in imps: + iw = imports.ImportWrapper(self._evaluator, i, + nested_resolve=True).follow() + if i.is_nested() and any(not isinstance(i, pr.Module) for i in iw): + analysis.add(self._evaluator, 'import-error', i) + for stmt in sorted(stmts, key=lambda obj: obj.start_pos): + if not (isinstance(stmt.parent, pr.ForFlow) + and stmt.parent.set_stmt == stmt): self._evaluator.eval_statement(stmt) ana = [a for a in self._evaluator.analysis if self.path == a.path] diff --git a/jedi/evaluate/analysis.py b/jedi/evaluate/analysis.py index c3627fa6..152b2f4c 100644 --- a/jedi/evaluate/analysis.py +++ b/jedi/evaluate/analysis.py @@ -103,3 +103,37 @@ def _check_for_exception_catch(evaluator, jedi_obj, exception): return True jedi_obj = jedi_obj.parent return False + + +def get_module_statements(module): + """ + Returns the statements used in a module. All these statements should be + evaluated to check for potential exceptions. + """ + def add_stmts(stmts): + new = set() + for stmt in stmts: + if isinstance(stmt, pr.Scope): + continue + if isinstance(stmt, pr.KeywordStatement): + stmt = stmt.stmt + if stmt is None: + continue + + for expression in stmt.expression_list(): + if isinstance(expression, pr.Array): + new |= add_stmts(expression.values) + + if isinstance(expression, pr.StatementElement): + for element in expression.generate_call_path(): + if isinstance(element, pr.Array): + new |= add_stmts(element.values) + new.add(stmt) + return new + + stmts = set() + imports = set() + for scope in module.walk(): + imports |= set(scope.imports) + stmts |= add_stmts(scope.statements) + return stmts, imports diff --git a/jedi/parser/representation.py b/jedi/parser/representation.py index 493482f7..64e8f959 100644 --- a/jedi/parser/representation.py +++ b/jedi/parser/representation.py @@ -351,6 +351,18 @@ class Scope(Simple, IsScope, DocstringMixin): return "<%s: %s@%s-%s>" % (type(self).__name__, name, self.start_pos[0], self.end_pos[0]) + def walk(self): + yield self + for s in self.subscopes: + for scope in s.walk(): + yield scope + + for r in self.statements: + while isinstance(r, Flow): + for scope in r.walk(): + yield scope + r = r.next + class Module(IsScope): """ @@ -792,22 +804,25 @@ class KeywordStatement(Base): For the following statements: `assert`, `del`, `global`, `nonlocal`, `raise`, `return`, `yield`, `pass`, `continue`, `break`, `return`, `yield`. """ - __slots__ = ('name', 'start_pos', '_stmt', 'parent') + __slots__ = ('name', 'start_pos', 'stmt', 'parent') def __init__(self, name, start_pos, parent, stmt=None): self.name = name self.start_pos = start_pos - self._stmt = stmt + self.stmt = stmt self.parent = parent if stmt is not None: stmt.parent = self + def __repr__(self): + return "<%s(%s): %s>" % (type(self).__name__, self.name, self.stmt) + def get_code(self): - if self._stmt is None: + if self.stmt is None: return "%s\n" % self.name else: - return '%s %s\n' % (self.name, self._stmt) + return '%s %s\n' % (self.name, self.stmt) def get_defined_names(self): return [] @@ -815,7 +830,7 @@ class KeywordStatement(Base): @property def end_pos(self): try: - return self._stmt.end_pos + return self.stmt.end_pos except AttributeError: return self.start_pos[0], self.start_pos[1] + len(self.name) diff --git a/test/static_analysis/attribute_error.py b/test/static_analysis/attribute_error.py index 3061502a..be7ce37e 100644 --- a/test/static_analysis/attribute_error.py +++ b/test/static_analysis/attribute_error.py @@ -44,9 +44,26 @@ c.something = None something = a something +# ----------------- +# Unused array variables should still raise attribute errors. +# ----------------- # should not raise anything. for loop_variable in [1, 2]: #! name-error x = undefined loop_variable + +#! name-error +for loop_variable in [1, 2, undefined]: + pass + +#! attribute-error +[1, ''.undefined_attr] + + +def return_one(something): + return 1 + +#! attribute-error +return_one(''.undefined_attribute) diff --git a/test/test_integration.py b/test/test_integration.py index 93c51978..ed980307 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -15,7 +15,7 @@ def assert_case_equal(case, actual, desired): """ assert actual == desired, """ Test %r failed. -actual = %s +actual = %s desired = %s """ % (case, actual, desired)