90 lines
2.9 KiB
Python
90 lines
2.9 KiB
Python
"""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`.
|
|
"""
|
|
pymodule = project.get_pymodule(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)
|