"""Finding bad name and attribute accesses `find_errors` function can be used to find possible bad name and attribute accesses. As an example:: errors = find_errors(project, project.get_resource('mod.py')) for error in errors: print '%s: %s' % (error.lineno, error.error) prints possible errors for ``mod.py`` file. TODO: * use task handles * reporting names at most once * attributes of extension modules that don't appear in extension_modules project config can be ignored * not calling `PyScope.get_inner_scope_for_line()` if it is a bottleneck; needs profiling * not reporting occurrences where rope cannot infer the object * rope saves multiple objects for some of the names in its objectdb use all of them not to give false positives * ... ;-) """ from rope.base import ast, evaluate, pyobjects def find_errors(project, resource): """Find possible bad name and attribute accesses It returns a list of `Error`\s. """ pymodule = project.pycore.resource_to_pyobject(resource) finder = _BadAccessFinder(pymodule) ast.walk(pymodule.get_ast(), finder) return finder.errors class _BadAccessFinder(object): def __init__(self, pymodule): self.pymodule = pymodule self.scope = pymodule.get_scope() self.errors = [] def _Name(self, node): if isinstance(node.ctx, (ast.Store, ast.Param)): return scope = self.scope.get_inner_scope_for_line(node.lineno) pyname = scope.lookup(node.id) if pyname is None: self._add_error(node, 'Unresolved variable') elif self._is_defined_after(scope, pyname, node.lineno): self._add_error(node, 'Defined later') def _Attribute(self, node): if not isinstance(node.ctx, ast.Store): scope = self.scope.get_inner_scope_for_line(node.lineno) pyname = evaluate.eval_node(scope, node.value) if pyname is not None and \ pyname.get_object() != pyobjects.get_unknown(): if node.attr not in pyname.get_object(): self._add_error(node, 'Unresolved attribute') ast.walk(node.value, self) def _add_error(self, node, msg): if isinstance(node, ast.Attribute): name = node.attr else: name = node.id if name != 'None': error = Error(node.lineno, msg + ' ' + name) self.errors.append(error) def _is_defined_after(self, scope, pyname, lineno): location = pyname.get_definition_location() if location is not None and location[1] is not None: if location[0] == self.pymodule and \ lineno <= location[1] <= scope.get_end(): return True class Error(object): def __init__(self, lineno, error): self.lineno = lineno self.error = error def __str__(self): return '%s: %s' % (self.lineno, self.error)