Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Side by Side Diff: third_party/pylint/checkers/typecheck.py

Issue 10447014: Add pylint to depot_tools. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Fix unittests. Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/pylint/checkers/string_format.py ('k') | third_party/pylint/checkers/utils.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2006-2010 LOGILAB S.A. (Paris, FRANCE).
2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr
3 #
4 # This program is free software; you can redistribute it and/or modify it under
5 # the terms of the GNU General Public License as published by the Free Software
6 # Foundation; either version 2 of the License, or (at your option) any later
7 # version.
8 #
9 # This program is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License along with
14 # this program; if not, write to the Free Software Foundation, Inc.,
15 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 """try to find more bugs in the code using astng inference capabilities
17 """
18
19 import re
20 import shlex
21
22 from logilab import astng
23 from logilab.astng import InferenceError, NotFoundError, YES, Instance
24
25 from pylint.interfaces import IASTNGChecker
26 from pylint.checkers import BaseChecker
27 from pylint.checkers.utils import safe_infer, is_super, check_messages
28
29 MSGS = {
30 'E1101': ('%s %r has no %r member',
31 'Used when a variable is accessed for an unexistent member.'),
32 'E1102': ('%s is not callable',
33 'Used when an object being called has been inferred to a non \
34 callable object'),
35 'E1103': ('%s %r has no %r member (but some types could not be inferred)',
36 'Used when a variable is accessed for an unexistent member, but \
37 astng was not able to interpret all possible types of this \
38 variable.'),
39 'E1111': ('Assigning to function call which doesn\'t return',
40 'Used when an assignment is done on a function call but the \
41 inferred function doesn\'t return anything.'),
42 'W1111': ('Assigning to function call which only returns None',
43 'Used when an assignment is done on a function call but the \
44 inferred function returns nothing but None.'),
45
46 'E1120': ('No value passed for parameter %s in function call',
47 'Used when a function call passes too few arguments.'),
48 'E1121': ('Too many positional arguments for function call',
49 'Used when a function call passes too many positional \
50 arguments.'),
51 'E1122': ('Duplicate keyword argument %r in function call',
52 'Used when a function call passes the same keyword argument \
53 multiple times.'),
54 'E1123': ('Passing unexpected keyword argument %r in function call',
55 'Used when a function call passes a keyword argument that \
56 doesn\'t correspond to one of the function\'s parameter names.'),
57 'E1124': ('Multiple values passed for parameter %r in function call',
58 'Used when a function call would result in assigning multiple \
59 values to a function parameter, one value from a positional \
60 argument and one from a keyword argument.'),
61 }
62
63 class TypeChecker(BaseChecker):
64 """try to find bugs in the code using type inference
65 """
66
67 __implements__ = (IASTNGChecker,)
68
69 # configuration section name
70 name = 'typecheck'
71 # messages
72 msgs = MSGS
73 priority = -1
74 # configuration options
75 options = (('ignore-mixin-members',
76 {'default' : True, 'type' : 'yn', 'metavar': '<y_or_n>',
77 'help' : 'Tells whether missing members accessed in mixin \
78 class should be ignored. A mixin class is detected if its name ends with \
79 "mixin" (case insensitive).'}
80 ),
81
82 ('ignored-classes',
83 {'default' : ('SQLObject',),
84 'type' : 'csv',
85 'metavar' : '<members names>',
86 'help' : 'List of classes names for which member attributes \
87 should not be checked (useful for classes with attributes dynamically set).'}
88 ),
89
90 ('zope',
91 {'default' : False, 'type' : 'yn', 'metavar': '<y_or_n>',
92 'help' : 'When zope mode is activated, add a predefined set \
93 of Zope acquired attributes to generated-members.'}
94 ),
95 ('generated-members',
96 {'default' : (
97 'REQUEST', 'acl_users', 'aq_parent'),
98 'type' : 'string',
99 'metavar' : '<members names>',
100 'help' : 'List of members which are set dynamically and \
101 missed by pylint inference system, and so shouldn\'t trigger E0201 when \
102 accessed. Python regular expressions are accepted.'}
103 ),
104 )
105
106 def open(self):
107 # do this in open since config not fully initialized in __init__
108 self.generated_members = list(self.config.generated_members)
109 if self.config.zope:
110 self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent'))
111
112 def visit_assattr(self, node):
113 if isinstance(node.ass_type(), astng.AugAssign):
114 self.visit_getattr(node)
115
116 def visit_delattr(self, node):
117 self.visit_getattr(node)
118
119 @check_messages('E1101', 'E1103')
120 def visit_getattr(self, node):
121 """check that the accessed attribute exists
122
123 to avoid to much false positives for now, we'll consider the code as
124 correct if a single of the inferred nodes has the accessed attribute.
125
126 function/method, super call and metaclasses are ignored
127 """
128 # generated_members may containt regular expressions
129 # (surrounded by quote `"` and followed by a comma `,`)
130 # REQUEST,aq_parent,"[a-zA-Z]+_set{1,2}"' =>
131 # ('REQUEST', 'aq_parent', '[a-zA-Z]+_set{1,2}')
132 if isinstance(self.config.generated_members, str):
133 gen = shlex.shlex(self.config.generated_members)
134 gen.whitespace += ','
135 self.config.generated_members = tuple(tok.strip('"') for tok in gen)
136 for pattern in self.config.generated_members:
137 # attribute is marked as generated, stop here
138 if re.match(pattern, node.attrname):
139 return
140 try:
141 infered = list(node.expr.infer())
142 except InferenceError:
143 return
144 # list of (node, nodename) which are missing the attribute
145 missingattr = set()
146 ignoremim = self.config.ignore_mixin_members
147 inference_failure = False
148 for owner in infered:
149 # skip yes object
150 if owner is YES:
151 inference_failure = True
152 continue
153 # skip None anyway
154 if isinstance(owner, astng.Const) and owner.value is None:
155 continue
156 # XXX "super" / metaclass call
157 if is_super(owner) or getattr(owner, 'type', None) == 'metaclass':
158 continue
159 name = getattr(owner, 'name', 'None')
160 if name in self.config.ignored_classes:
161 continue
162 if ignoremim and name[-5:].lower() == 'mixin':
163 continue
164 try:
165 if not [n for n in owner.getattr(node.attrname)
166 if not isinstance(n.statement(), astng.AugAssign)]:
167 missingattr.add((owner, name))
168 continue
169 except AttributeError:
170 # XXX method / function
171 continue
172 except NotFoundError:
173 if isinstance(owner, astng.Function) and owner.decorators:
174 continue
175 if isinstance(owner, Instance) and owner.has_dynamic_getattr():
176 continue
177 # explicit skipping of optparse'Values class
178 if owner.name == 'Values' and owner.root().name == 'optparse':
179 continue
180 missingattr.add((owner, name))
181 continue
182 # stop on the first found
183 break
184 else:
185 # we have not found any node with the attributes, display the
186 # message for infered nodes
187 done = set()
188 for owner, name in missingattr:
189 if isinstance(owner, Instance):
190 actual = owner._proxied
191 else:
192 actual = owner
193 if actual in done:
194 continue
195 done.add(actual)
196 if inference_failure:
197 msgid = 'E1103'
198 else:
199 msgid = 'E1101'
200 self.add_message(msgid, node=node,
201 args=(owner.display_type(), name,
202 node.attrname))
203
204
205 def visit_assign(self, node):
206 """check that if assigning to a function call, the function is
207 possibly returning something valuable
208 """
209 if not isinstance(node.value, astng.CallFunc):
210 return
211 function_node = safe_infer(node.value.func)
212 # skip class, generator and incomplete function definition
213 if not (isinstance(function_node, astng.Function) and
214 function_node.root().fully_defined()):
215 return
216 if function_node.is_generator() \
217 or function_node.is_abstract(pass_is_abstract=False):
218 return
219 returns = list(function_node.nodes_of_class(astng.Return,
220 skip_klass=astng.Function))
221 if len(returns) == 0:
222 self.add_message('E1111', node=node)
223 else:
224 for rnode in returns:
225 if not (isinstance(rnode.value, astng.Const)
226 and rnode.value.value is None):
227 break
228 else:
229 self.add_message('W1111', node=node)
230
231 def visit_callfunc(self, node):
232 """check that called functions/methods are inferred to callable objects,
233 and that the arguments passed to the function match the parameters in
234 the inferred function's definition
235 """
236
237 # Build the set of keyword arguments, checking for duplicate keywords,
238 # and count the positional arguments.
239 keyword_args = set()
240 num_positional_args = 0
241 for arg in node.args:
242 if isinstance(arg, astng.Keyword):
243 keyword = arg.arg
244 if keyword in keyword_args:
245 self.add_message('E1122', node=node, args=keyword)
246 keyword_args.add(keyword)
247 else:
248 num_positional_args += 1
249
250 called = safe_infer(node.func)
251 # only function, generator and object defining __call__ are allowed
252 if called is not None and not called.callable():
253 self.add_message('E1102', node=node, args=node.func.as_string())
254
255 # Note that BoundMethod is a subclass of UnboundMethod (huh?), so must
256 # come first in this 'if..else'.
257 if isinstance(called, astng.BoundMethod):
258 # Bound methods have an extra implicit 'self' argument.
259 num_positional_args += 1
260 elif isinstance(called, astng.UnboundMethod):
261 if called.decorators is not None:
262 for d in called.decorators.nodes:
263 if isinstance(d, astng.Name) and (d.name == 'classmethod'):
264 # Class methods have an extra implicit 'cls' argument.
265 num_positional_args += 1
266 break
267 elif (isinstance(called, astng.Function) or
268 isinstance(called, astng.Lambda)):
269 pass
270 else:
271 return
272
273 if called.args.args is None:
274 # Built-in functions have no argument information.
275 return
276
277 if len( called.argnames() ) != len( set( called.argnames() ) ):
278 # Duplicate parameter name (see E9801). We can't really make sense
279 # of the function call in this case, so just return.
280 return
281
282 # Analyze the list of formal parameters.
283 num_mandatory_parameters = len(called.args.args) - len(called.args.defau lts)
284 parameters = []
285 parameter_name_to_index = {}
286 for i, arg in enumerate(called.args.args):
287 if isinstance(arg, astng.Tuple):
288 name = None
289 # Don't store any parameter names within the tuple, since those
290 # are not assignable from keyword arguments.
291 else:
292 if isinstance(arg, astng.Keyword):
293 name = arg.arg
294 else:
295 assert isinstance(arg, astng.AssName)
296 # This occurs with:
297 # def f( (a), (b) ): pass
298 name = arg.name
299 parameter_name_to_index[name] = i
300 if i >= num_mandatory_parameters:
301 defval = called.args.defaults[i - num_mandatory_parameters]
302 else:
303 defval = None
304 parameters.append([(name, defval), False])
305
306 # Match the supplied arguments against the function parameters.
307
308 # 1. Match the positional arguments.
309 for i in range(num_positional_args):
310 if i < len(parameters):
311 parameters[i][1] = True
312 elif called.args.vararg is not None:
313 # The remaining positional arguments get assigned to the *args
314 # parameter.
315 break
316 else:
317 # Too many positional arguments.
318 self.add_message('E1121', node=node)
319 break
320
321 # 2. Match the keyword arguments.
322 for keyword in keyword_args:
323 if keyword in parameter_name_to_index:
324 i = parameter_name_to_index[keyword]
325 if parameters[i][1]:
326 # Duplicate definition of function parameter.
327 self.add_message('E1124', node=node, args=keyword)
328 else:
329 parameters[i][1] = True
330 elif called.args.kwarg is not None:
331 # The keyword argument gets assigned to the **kwargs parameter.
332 pass
333 else:
334 # Unexpected keyword argument.
335 self.add_message('E1123', node=node, args=keyword)
336
337 # 3. Match the *args, if any. Note that Python actually processes
338 # *args _before_ any keyword arguments, but we wait until after
339 # looking at the keyword arguments so as to make a more conservative
340 # guess at how many values are in the *args sequence.
341 if node.starargs is not None:
342 for i in range(num_positional_args, len(parameters)):
343 [(name, defval), assigned] = parameters[i]
344 # Assume that *args provides just enough values for all
345 # non-default parameters after the last parameter assigned by
346 # the positional arguments but before the first parameter
347 # assigned by the keyword arguments. This is the best we can
348 # get without generating any false positives.
349 if (defval is not None) or assigned:
350 break
351 parameters[i][1] = True
352
353 # 4. Match the **kwargs, if any.
354 if node.kwargs is not None:
355 for i, [(name, defval), assigned] in enumerate(parameters):
356 # Assume that *kwargs provides values for all remaining
357 # unassigned named parameters.
358 if name is not None:
359 parameters[i][1] = True
360 else:
361 # **kwargs can't assign to tuples.
362 pass
363
364 # Check that any parameters without a default have been assigned
365 # values.
366 for [(name, defval), assigned] in parameters:
367 if (defval is None) and not assigned:
368 if name is None:
369 display = '<tuple>'
370 else:
371 display_name = repr(name)
372 self.add_message('E1120', node=node, args=display_name)
373
374 def register(linter):
375 """required method to auto register this checker """
376 linter.register_checker(TypeChecker(linter))
OLDNEW
« no previous file with comments | « third_party/pylint/checkers/string_format.py ('k') | third_party/pylint/checkers/utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698