-
Notifications
You must be signed in to change notification settings - Fork 161
/
finderrors.py
90 lines (72 loc) · 2.87 KB
/
finderrors.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""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)
finder.visit(pymodule.get_ast())
return finder.errors
class _BadAccessFinder(ast.RopeNodeVisitor):
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")
self.visit(node.value)
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:
def __init__(self, lineno, error):
self.lineno = lineno
self.error = error
def __str__(self):
return f"{self.lineno}: {self.error}"