]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | """ |
2 | Module for statical analysis. | |
3 | """ | |
4 | from parso.python import tree | |
5 | ||
6 | from jedi import debug | |
7 | from jedi.inference.helpers import is_string | |
8 | ||
9 | ||
10 | CODES = { | |
11 | 'attribute-error': (1, AttributeError, 'Potential AttributeError.'), | |
12 | 'name-error': (2, NameError, 'Potential NameError.'), | |
13 | 'import-error': (3, ImportError, 'Potential ImportError.'), | |
14 | 'type-error-too-many-arguments': (4, TypeError, None), | |
15 | 'type-error-too-few-arguments': (5, TypeError, None), | |
16 | 'type-error-keyword-argument': (6, TypeError, None), | |
17 | 'type-error-multiple-values': (7, TypeError, None), | |
18 | 'type-error-star-star': (8, TypeError, None), | |
19 | 'type-error-star': (9, TypeError, None), | |
20 | 'type-error-operation': (10, TypeError, None), | |
21 | 'type-error-not-iterable': (11, TypeError, None), | |
22 | 'type-error-isinstance': (12, TypeError, None), | |
23 | 'type-error-not-subscriptable': (13, TypeError, None), | |
24 | 'value-error-too-many-values': (14, ValueError, None), | |
25 | 'value-error-too-few-values': (15, ValueError, None), | |
26 | } | |
27 | ||
28 | ||
29 | class Error: | |
30 | def __init__(self, name, module_path, start_pos, message=None): | |
31 | self.path = module_path | |
32 | self._start_pos = start_pos | |
33 | self.name = name | |
34 | if message is None: | |
35 | message = CODES[self.name][2] | |
36 | self.message = message | |
37 | ||
38 | @property | |
39 | def line(self): | |
40 | return self._start_pos[0] | |
41 | ||
42 | @property | |
43 | def column(self): | |
44 | return self._start_pos[1] | |
45 | ||
46 | @property | |
47 | def code(self): | |
48 | # The class name start | |
49 | first = self.__class__.__name__[0] | |
50 | return first + str(CODES[self.name][0]) | |
51 | ||
52 | def __str__(self): | |
53 | return '%s:%s:%s: %s %s' % (self.path, self.line, self.column, | |
54 | self.code, self.message) | |
55 | ||
56 | def __eq__(self, other): | |
57 | return (self.path == other.path and self.name == other.name | |
58 | and self._start_pos == other._start_pos) | |
59 | ||
60 | def __ne__(self, other): | |
61 | return not self.__eq__(other) | |
62 | ||
63 | def __hash__(self): | |
64 | return hash((self.path, self._start_pos, self.name)) | |
65 | ||
66 | def __repr__(self): | |
67 | return '<%s %s: %s@%s,%s>' % (self.__class__.__name__, | |
68 | self.name, self.path, | |
69 | self._start_pos[0], self._start_pos[1]) | |
70 | ||
71 | ||
72 | class Warning(Error): | |
73 | pass | |
74 | ||
75 | ||
76 | def add(node_context, error_name, node, message=None, typ=Error, payload=None): | |
77 | exception = CODES[error_name][1] | |
78 | if _check_for_exception_catch(node_context, node, exception, payload): | |
79 | return | |
80 | ||
81 | # TODO this path is probably not right | |
82 | module_context = node_context.get_root_context() | |
83 | module_path = module_context.py__file__() | |
84 | issue_instance = typ(error_name, module_path, node.start_pos, message) | |
85 | debug.warning(str(issue_instance), format=False) | |
86 | node_context.inference_state.analysis.append(issue_instance) | |
87 | return issue_instance | |
88 | ||
89 | ||
90 | def _check_for_setattr(instance): | |
91 | """ | |
92 | Check if there's any setattr method inside an instance. If so, return True. | |
93 | """ | |
94 | module = instance.get_root_context() | |
95 | node = module.tree_node | |
96 | if node is None: | |
97 | # If it's a compiled module or doesn't have a tree_node | |
98 | return False | |
99 | ||
100 | try: | |
101 | stmt_names = node.get_used_names()['setattr'] | |
102 | except KeyError: | |
103 | return False | |
104 | ||
105 | return any(node.start_pos < n.start_pos < node.end_pos | |
106 | # Check if it's a function called setattr. | |
107 | and not (n.parent.type == 'funcdef' and n.parent.name == n) | |
108 | for n in stmt_names) | |
109 | ||
110 | ||
111 | def add_attribute_error(name_context, lookup_value, name): | |
112 | message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name)) | |
113 | # Check for __getattr__/__getattribute__ existance and issue a warning | |
114 | # instead of an error, if that happens. | |
115 | typ = Error | |
116 | if lookup_value.is_instance() and not lookup_value.is_compiled(): | |
117 | # TODO maybe make a warning for __getattr__/__getattribute__ | |
118 | ||
119 | if _check_for_setattr(lookup_value): | |
120 | typ = Warning | |
121 | ||
122 | payload = lookup_value, name | |
123 | add(name_context, 'attribute-error', name, message, typ, payload) | |
124 | ||
125 | ||
126 | def _check_for_exception_catch(node_context, jedi_name, exception, payload=None): | |
127 | """ | |
128 | Checks if a jedi object (e.g. `Statement`) sits inside a try/catch and | |
129 | doesn't count as an error (if equal to `exception`). | |
130 | Also checks `hasattr` for AttributeErrors and uses the `payload` to compare | |
131 | it. | |
132 | Returns True if the exception was catched. | |
133 | """ | |
134 | def check_match(cls, exception): | |
135 | if not cls.is_class(): | |
136 | return False | |
137 | ||
138 | for python_cls in exception.mro(): | |
139 | if cls.py__name__() == python_cls.__name__ \ | |
140 | and cls.parent_context.is_builtins_module(): | |
141 | return True | |
142 | return False | |
143 | ||
144 | def check_try_for_except(obj, exception): | |
145 | # Only nodes in try | |
146 | iterator = iter(obj.children) | |
147 | for branch_type in iterator: | |
148 | next(iterator) # The colon | |
149 | suite = next(iterator) | |
150 | if branch_type == 'try' \ | |
151 | and not (branch_type.start_pos < jedi_name.start_pos <= suite.end_pos): | |
152 | return False | |
153 | ||
154 | for node in obj.get_except_clause_tests(): | |
155 | if node is None: | |
156 | return True # An exception block that catches everything. | |
157 | else: | |
158 | except_classes = node_context.infer_node(node) | |
159 | for cls in except_classes: | |
160 | from jedi.inference.value import iterable | |
161 | if isinstance(cls, iterable.Sequence) and \ | |
162 | cls.array_type == 'tuple': | |
163 | # multiple exceptions | |
164 | for lazy_value in cls.py__iter__(): | |
165 | for typ in lazy_value.infer(): | |
166 | if check_match(typ, exception): | |
167 | return True | |
168 | else: | |
169 | if check_match(cls, exception): | |
170 | return True | |
171 | ||
172 | def check_hasattr(node, suite): | |
173 | try: | |
174 | assert suite.start_pos <= jedi_name.start_pos < suite.end_pos | |
175 | assert node.type in ('power', 'atom_expr') | |
176 | base = node.children[0] | |
177 | assert base.type == 'name' and base.value == 'hasattr' | |
178 | trailer = node.children[1] | |
179 | assert trailer.type == 'trailer' | |
180 | arglist = trailer.children[1] | |
181 | assert arglist.type == 'arglist' | |
182 | from jedi.inference.arguments import TreeArguments | |
183 | args = TreeArguments(node_context.inference_state, node_context, arglist) | |
184 | unpacked_args = list(args.unpack()) | |
185 | # Arguments should be very simple | |
186 | assert len(unpacked_args) == 2 | |
187 | ||
188 | # Check name | |
189 | key, lazy_value = unpacked_args[1] | |
190 | names = list(lazy_value.infer()) | |
191 | assert len(names) == 1 and is_string(names[0]) | |
192 | assert names[0].get_safe_value() == payload[1].value | |
193 | ||
194 | # Check objects | |
195 | key, lazy_value = unpacked_args[0] | |
196 | objects = lazy_value.infer() | |
197 | return payload[0] in objects | |
198 | except AssertionError: | |
199 | return False | |
200 | ||
201 | obj = jedi_name | |
202 | while obj is not None and not isinstance(obj, (tree.Function, tree.Class)): | |
203 | if isinstance(obj, tree.Flow): | |
204 | # try/except catch check | |
205 | if obj.type == 'try_stmt' and check_try_for_except(obj, exception): | |
206 | return True | |
207 | # hasattr check | |
208 | if exception == AttributeError and obj.type in ('if_stmt', 'while_stmt'): | |
209 | if check_hasattr(obj.children[1], obj.children[3]): | |
210 | return True | |
211 | obj = obj.parent | |
212 | ||
213 | return False |