gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / pylint / checkers / base.py @ 745
History | View | Annotate | Download (84.4 KB)
1 |
# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
|
---|---|
2 |
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
3 |
# Copyright (c) 2009-2010 Arista Networks, Inc.
|
4 |
#
|
5 |
# This program is free software; you can redistribute it and/or modify it under
|
6 |
# the terms of the GNU General Public License as published by the Free Software
|
7 |
# Foundation; either version 2 of the License, or (at your option) any later
|
8 |
# version.
|
9 |
#
|
10 |
# This program is distributed in the hope that it will be useful, but WITHOUT
|
11 |
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
12 |
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
13 |
#
|
14 |
# You should have received a copy of the GNU General Public License along with
|
15 |
# this program; if not, write to the Free Software Foundation, Inc.,
|
16 |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
17 |
"""basic checker for Python code"""
|
18 |
|
19 |
import collections |
20 |
import itertools |
21 |
import sys |
22 |
import re |
23 |
|
24 |
import six |
25 |
from six.moves import zip # pylint: disable=redefined-builtin |
26 |
|
27 |
import astroid |
28 |
import astroid.bases |
29 |
import astroid.scoped_nodes |
30 |
from astroid import are_exclusive, InferenceError |
31 |
|
32 |
from pylint.interfaces import (IAstroidChecker, ITokenChecker, INFERENCE, |
33 |
INFERENCE_FAILURE, HIGH) |
34 |
from pylint.utils import EmptyReport, deprecated_option |
35 |
from pylint.reporters import diff_string |
36 |
from pylint.checkers import BaseChecker, BaseTokenChecker |
37 |
from pylint.checkers.utils import ( |
38 |
check_messages, |
39 |
clobber_in_except, |
40 |
is_builtin_object, |
41 |
is_inside_except, |
42 |
overrides_a_method, |
43 |
get_argument_from_call, |
44 |
node_frame_class, |
45 |
NoSuchArgumentError, |
46 |
error_of_type, |
47 |
unimplemented_abstract_methods, |
48 |
has_known_bases, |
49 |
safe_infer |
50 |
) |
51 |
from pylint.reporters.ureports.nodes import Table |
52 |
|
53 |
|
54 |
# regex for class/function/variable/constant name
|
55 |
CLASS_NAME_RGX = re.compile('[A-Z_][a-zA-Z0-9]+$')
|
56 |
MOD_NAME_RGX = re.compile('(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$')
|
57 |
CONST_NAME_RGX = re.compile('(([A-Z_][A-Z0-9_]*)|(__.*__))$')
|
58 |
COMP_VAR_RGX = re.compile('[A-Za-z_][A-Za-z0-9_]*$')
|
59 |
DEFAULT_NAME_RGX = re.compile('[a-z_][a-z0-9_]{2,30}$')
|
60 |
CLASS_ATTRIBUTE_RGX = re.compile(r'([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$')
|
61 |
# do not require a doc string on private/system methods
|
62 |
NO_REQUIRED_DOC_RGX = re.compile('^_')
|
63 |
REVERSED_PROTOCOL_METHOD = '__reversed__'
|
64 |
SEQUENCE_PROTOCOL_METHODS = ('__getitem__', '__len__') |
65 |
REVERSED_METHODS = (SEQUENCE_PROTOCOL_METHODS, |
66 |
(REVERSED_PROTOCOL_METHOD, )) |
67 |
TYPECHECK_COMPARISON_OPERATORS = frozenset(('is', 'is not', '==', |
68 |
'!=', 'in', 'not in')) |
69 |
LITERAL_NODE_TYPES = (astroid.Const, astroid.Dict, astroid.List, astroid.Set) |
70 |
UNITTEST_CASE = 'unittest.case'
|
71 |
BUILTINS = six.moves.builtins.__name__ |
72 |
TYPE_QNAME = "%s.type" % BUILTINS
|
73 |
PY33 = sys.version_info >= (3, 3) |
74 |
PY3K = sys.version_info >= (3, 0) |
75 |
PY35 = sys.version_info >= (3, 5) |
76 |
BAD_FUNCTIONS = ['map', 'filter'] |
77 |
if sys.version_info < (3, 0): |
78 |
BAD_FUNCTIONS.append('input')
|
79 |
|
80 |
# Some hints regarding the use of bad builtins.
|
81 |
BUILTIN_HINTS = { |
82 |
'map': 'Using a list comprehension can be clearer.', |
83 |
} |
84 |
BUILTIN_HINTS['filter'] = BUILTIN_HINTS['map'] |
85 |
|
86 |
# Name categories that are always consistent with all naming conventions.
|
87 |
EXEMPT_NAME_CATEGORIES = set(('exempt', 'ignore')) |
88 |
|
89 |
# A mapping from builtin-qname -> symbol, to be used when generating messages
|
90 |
# about dangerous default values as arguments
|
91 |
DEFAULT_ARGUMENT_SYMBOLS = dict(
|
92 |
zip(['.'.join([BUILTINS, x]) for x in ('set', 'dict', 'list')], |
93 |
['set()', '{}', '[]']) |
94 |
) |
95 |
REVERSED_COMPS = {'<': '>', '<=': '>=', '>': '<', '>=': '<='} |
96 |
|
97 |
del re
|
98 |
|
99 |
def _redefines_import(node): |
100 |
""" Detect that the given node (AssName) is inside an
|
101 |
exception handler and redefines an import from the tryexcept body.
|
102 |
Returns True if the node redefines an import, False otherwise.
|
103 |
"""
|
104 |
current = node |
105 |
while current and not isinstance(current.parent, astroid.ExceptHandler): |
106 |
current = current.parent |
107 |
if not current or not error_of_type(current.parent, ImportError): |
108 |
return False |
109 |
try_block = current.parent.parent |
110 |
for import_node in try_block.nodes_of_class((astroid.ImportFrom, astroid.Import)): |
111 |
for name, alias in import_node.names: |
112 |
if alias:
|
113 |
if alias == node.name:
|
114 |
return True |
115 |
elif name == node.name:
|
116 |
return True |
117 |
return False |
118 |
|
119 |
def in_loop(node): |
120 |
"""return True if the node is inside a kind of for loop"""
|
121 |
parent = node.parent |
122 |
while parent is not None: |
123 |
if isinstance(parent, (astroid.For, astroid.ListComp, astroid.SetComp, |
124 |
astroid.DictComp, astroid.GeneratorExp)): |
125 |
return True |
126 |
parent = parent.parent |
127 |
return False |
128 |
|
129 |
def in_nested_list(nested_list, obj): |
130 |
"""return true if the object is an element of <nested_list> or of a nested
|
131 |
list
|
132 |
"""
|
133 |
for elmt in nested_list: |
134 |
if isinstance(elmt, (list, tuple)): |
135 |
if in_nested_list(elmt, obj):
|
136 |
return True |
137 |
elif elmt == obj:
|
138 |
return True |
139 |
return False |
140 |
|
141 |
def _loop_exits_early(loop): |
142 |
"""Returns true if a loop has a break statement in its body."""
|
143 |
loop_nodes = (astroid.For, astroid.While) |
144 |
# Loop over body explicitly to avoid matching break statements
|
145 |
# in orelse.
|
146 |
for child in loop.body: |
147 |
if isinstance(child, loop_nodes): |
148 |
# break statement may be in orelse of child loop.
|
149 |
# pylint: disable=superfluous-parens
|
150 |
for orelse in (child.orelse or ()): |
151 |
for _ in orelse.nodes_of_class(astroid.Break, skip_klass=loop_nodes): |
152 |
return True |
153 |
continue
|
154 |
for _ in child.nodes_of_class(astroid.Break, skip_klass=loop_nodes): |
155 |
return True |
156 |
return False |
157 |
|
158 |
def _is_multi_naming_match(match, node_type, confidence): |
159 |
return (match is not None and |
160 |
match.lastgroup is not None and |
161 |
match.lastgroup not in EXEMPT_NAME_CATEGORIES |
162 |
and (node_type != 'method' or confidence != INFERENCE_FAILURE)) |
163 |
|
164 |
|
165 |
if sys.version_info < (3, 0): |
166 |
PROPERTY_CLASSES = set(('__builtin__.property', 'abc.abstractproperty')) |
167 |
else:
|
168 |
PROPERTY_CLASSES = set(('builtins.property', 'abc.abstractproperty')) |
169 |
|
170 |
|
171 |
def _determine_function_name_type(node): |
172 |
"""Determine the name type whose regex the a function's name should match.
|
173 |
|
174 |
:param node: A function node.
|
175 |
:returns: One of ('function', 'method', 'attr')
|
176 |
"""
|
177 |
if not node.is_method(): |
178 |
return 'function' |
179 |
if node.decorators:
|
180 |
decorators = node.decorators.nodes |
181 |
else:
|
182 |
decorators = [] |
183 |
for decorator in decorators: |
184 |
# If the function is a property (decorated with @property
|
185 |
# or @abc.abstractproperty), the name type is 'attr'.
|
186 |
if (isinstance(decorator, astroid.Name) or |
187 |
(isinstance(decorator, astroid.Attribute) and |
188 |
decorator.attrname == 'abstractproperty')):
|
189 |
infered = safe_infer(decorator) |
190 |
if infered and infered.qname() in PROPERTY_CLASSES: |
191 |
return 'attr' |
192 |
# If the function is decorated using the prop_method.{setter,getter}
|
193 |
# form, treat it like an attribute as well.
|
194 |
elif (isinstance(decorator, astroid.Attribute) and |
195 |
decorator.attrname in ('setter', 'deleter')): |
196 |
return 'attr' |
197 |
return 'method' |
198 |
|
199 |
|
200 |
def _is_none(node): |
201 |
return (node is None or |
202 |
(isinstance(node, astroid.Const) and node.value is None) or |
203 |
(isinstance(node, astroid.Name) and node.name == 'None') |
204 |
) |
205 |
|
206 |
|
207 |
def _has_abstract_methods(node): |
208 |
"""
|
209 |
Determine if the given `node` has abstract methods.
|
210 |
|
211 |
The methods should be made abstract by decorating them
|
212 |
with `abc` decorators.
|
213 |
"""
|
214 |
return len(unimplemented_abstract_methods(node)) > 0 |
215 |
|
216 |
|
217 |
def report_by_type_stats(sect, stats, old_stats): |
218 |
"""make a report of
|
219 |
|
220 |
* percentage of different types documented
|
221 |
* percentage of different types with a bad name
|
222 |
"""
|
223 |
# percentage of different types documented and/or with a bad name
|
224 |
nice_stats = {} |
225 |
for node_type in ('module', 'class', 'method', 'function'): |
226 |
try:
|
227 |
total = stats[node_type] |
228 |
except KeyError: |
229 |
raise EmptyReport()
|
230 |
nice_stats[node_type] = {} |
231 |
if total != 0: |
232 |
try:
|
233 |
documented = total - stats['undocumented_'+node_type]
|
234 |
percent = (documented * 100.) / total
|
235 |
nice_stats[node_type]['percent_documented'] = '%.2f' % percent |
236 |
except KeyError: |
237 |
nice_stats[node_type]['percent_documented'] = 'NC' |
238 |
try:
|
239 |
percent = (stats['badname_'+node_type] * 100.) / total |
240 |
nice_stats[node_type]['percent_badname'] = '%.2f' % percent |
241 |
except KeyError: |
242 |
nice_stats[node_type]['percent_badname'] = 'NC' |
243 |
lines = ('type', 'number', 'old number', 'difference', |
244 |
'%documented', '%badname') |
245 |
for node_type in ('module', 'class', 'method', 'function'): |
246 |
new = stats[node_type] |
247 |
old = old_stats.get(node_type, None)
|
248 |
if old is not None: |
249 |
diff_str = diff_string(old, new) |
250 |
else:
|
251 |
old, diff_str = 'NC', 'NC' |
252 |
lines += (node_type, str(new), str(old), diff_str, |
253 |
nice_stats[node_type].get('percent_documented', '0'), |
254 |
nice_stats[node_type].get('percent_badname', '0')) |
255 |
sect.append(Table(children=lines, cols=6, rheaders=1)) |
256 |
|
257 |
def redefined_by_decorator(node): |
258 |
"""return True if the object is a method redefined via decorator.
|
259 |
|
260 |
For example:
|
261 |
@property
|
262 |
def x(self): return self._x
|
263 |
@x.setter
|
264 |
def x(self, value): self._x = value
|
265 |
"""
|
266 |
if node.decorators:
|
267 |
for decorator in node.decorators.nodes: |
268 |
if (isinstance(decorator, astroid.Attribute) and |
269 |
getattr(decorator.expr, 'name', None) == node.name): |
270 |
return True |
271 |
return False |
272 |
|
273 |
|
274 |
def _node_type(node): |
275 |
"""Return the inferred type for `node`
|
276 |
|
277 |
If there is more than one possible type, or if inferred type is YES or None,
|
278 |
return None
|
279 |
"""
|
280 |
# check there is only one possible type for the assign node. Else we
|
281 |
# don't handle it for now
|
282 |
types = set()
|
283 |
try:
|
284 |
for var_type in node.infer(): |
285 |
if var_type == astroid.YES or _is_none(var_type): |
286 |
continue
|
287 |
types.add(var_type) |
288 |
if len(types) > 1: |
289 |
return
|
290 |
except InferenceError:
|
291 |
return
|
292 |
return types.pop() if types else None |
293 |
|
294 |
|
295 |
class _BasicChecker(BaseChecker): |
296 |
__implements__ = IAstroidChecker |
297 |
name = 'basic'
|
298 |
|
299 |
class BasicErrorChecker(_BasicChecker): |
300 |
msgs = { |
301 |
'E0100': ('__init__ method is a generator', |
302 |
'init-is-generator',
|
303 |
'Used when the special class method __init__ is turned into a '
|
304 |
'generator by a yield in its body.'),
|
305 |
'E0101': ('Explicit return in __init__', |
306 |
'return-in-init',
|
307 |
'Used when the special class method __init__ has an explicit '
|
308 |
'return value.'),
|
309 |
'E0102': ('%s already defined line %s', |
310 |
'function-redefined',
|
311 |
'Used when a function / class / method is redefined.'),
|
312 |
'E0103': ('%r not properly in loop', |
313 |
'not-in-loop',
|
314 |
'Used when break or continue keywords are used outside a loop.'),
|
315 |
'E0104': ('Return outside function', |
316 |
'return-outside-function',
|
317 |
'Used when a "return" statement is found outside a function or '
|
318 |
'method.'),
|
319 |
'E0105': ('Yield outside function', |
320 |
'yield-outside-function',
|
321 |
'Used when a "yield" statement is found outside a function or '
|
322 |
'method.'),
|
323 |
'E0106': ('Return with argument inside generator', |
324 |
'return-arg-in-generator',
|
325 |
'Used when a "return" statement with an argument is found '
|
326 |
'outside in a generator function or method (e.g. with some '
|
327 |
'"yield" statements).',
|
328 |
{'maxversion': (3, 3)}), |
329 |
'E0107': ("Use of the non-existent %s operator", |
330 |
'nonexistent-operator',
|
331 |
"Used when you attempt to use the C-style pre-increment or"
|
332 |
"pre-decrement operator -- and ++, which doesn't exist in Python."),
|
333 |
'E0108': ('Duplicate argument name %s in function definition', |
334 |
'duplicate-argument-name',
|
335 |
'Duplicate argument names in function definitions are syntax'
|
336 |
' errors.'),
|
337 |
'E0110': ('Abstract class %r with abstract methods instantiated', |
338 |
'abstract-class-instantiated',
|
339 |
'Used when an abstract class with `abc.ABCMeta` as metaclass '
|
340 |
'has abstract methods and is instantiated.'),
|
341 |
'W0120': ('Else clause on loop without a break statement', |
342 |
'useless-else-on-loop',
|
343 |
'Loops should only have an else clause if they can exit early '
|
344 |
'with a break statement, otherwise the statements under else '
|
345 |
'should be on the same scope as the loop itself.'),
|
346 |
'E0112': ('More than one starred expression in assignment', |
347 |
'too-many-star-expressions',
|
348 |
'Emitted when there are more than one starred '
|
349 |
'expressions (`*x`) in an assignment. This is a SyntaxError.',
|
350 |
{'minversion': (3, 0)}), |
351 |
'E0113': ('Starred assignment target must be in a list or tuple', |
352 |
'invalid-star-assignment-target',
|
353 |
'Emitted when a star expression is used as a starred '
|
354 |
'assignment target.',
|
355 |
{'minversion': (3, 0)}), |
356 |
'E0114': ('Can use starred expression only in assignment target', |
357 |
'star-needs-assignment-target',
|
358 |
'Emitted when a star expression is not used in an '
|
359 |
'assignment target.',
|
360 |
{'minversion': (3, 0)}), |
361 |
'E0115': ('Name %r is nonlocal and global', |
362 |
'nonlocal-and-global',
|
363 |
'Emitted when a name is both nonlocal and global.',
|
364 |
{'minversion': (3, 0)}), |
365 |
'E0116': ("'continue' not supported inside 'finally' clause", |
366 |
'continue-in-finally',
|
367 |
'Emitted when the `continue` keyword is found '
|
368 |
'inside a finally clause, which is a SyntaxError.'),
|
369 |
'E0117': ("nonlocal name %s found without binding", |
370 |
'nonlocal-without-binding',
|
371 |
'Emitted when a nonlocal variable does not have an attached '
|
372 |
'name somewhere in the parent scopes',
|
373 |
{'minversion': (3, 0)}), |
374 |
} |
375 |
|
376 |
@check_messages('function-redefined') |
377 |
def visit_classdef(self, node): |
378 |
self._check_redefinition('class', node) |
379 |
|
380 |
@check_messages('too-many-star-expressions', |
381 |
'invalid-star-assignment-target')
|
382 |
def visit_assign(self, node): |
383 |
starred = list(node.targets[0].nodes_of_class(astroid.Starred)) |
384 |
if len(starred) > 1: |
385 |
self.add_message('too-many-star-expressions', node=node) |
386 |
|
387 |
# Check *a = b
|
388 |
if isinstance(node.targets[0], astroid.Starred): |
389 |
self.add_message('invalid-star-assignment-target', node=node) |
390 |
|
391 |
@check_messages('star-needs-assignment-target') |
392 |
def visit_starred(self, node): |
393 |
"""Check that a Starred expression is used in an assignment target."""
|
394 |
if isinstance(node.parent, astroid.Call): |
395 |
# f(*args) is converted to Call(args=[Starred]), so ignore
|
396 |
# them for this check.
|
397 |
return
|
398 |
if PY35 and isinstance(node.parent, |
399 |
(astroid.List, astroid.Tuple, |
400 |
astroid.Set, astroid.Dict)): |
401 |
# PEP 448 unpacking.
|
402 |
return
|
403 |
|
404 |
stmt = node.statement() |
405 |
if not isinstance(stmt, astroid.Assign): |
406 |
return
|
407 |
|
408 |
if stmt.value is node or stmt.value.parent_of(node): |
409 |
self.add_message('star-needs-assignment-target', node=node) |
410 |
|
411 |
@check_messages('init-is-generator', 'return-in-init', |
412 |
'function-redefined', 'return-arg-in-generator', |
413 |
'duplicate-argument-name', 'nonlocal-and-global') |
414 |
def visit_functiondef(self, node): |
415 |
self._check_nonlocal_and_global(node)
|
416 |
if not redefined_by_decorator(node): |
417 |
self._check_redefinition(node.is_method() and 'method' or 'function', node) |
418 |
# checks for max returns, branch, return in __init__
|
419 |
returns = node.nodes_of_class(astroid.Return, |
420 |
skip_klass=(astroid.FunctionDef, |
421 |
astroid.ClassDef)) |
422 |
if node.is_method() and node.name == '__init__': |
423 |
if node.is_generator():
|
424 |
self.add_message('init-is-generator', node=node) |
425 |
else:
|
426 |
values = [r.value for r in returns] |
427 |
# Are we returning anything but None from constructors
|
428 |
if [v for v in values if not _is_none(v)]: |
429 |
self.add_message('return-in-init', node=node) |
430 |
elif node.is_generator():
|
431 |
# make sure we don't mix non-None returns and yields
|
432 |
if not PY33: |
433 |
for retnode in returns: |
434 |
if isinstance(retnode.value, astroid.Const) and \ |
435 |
retnode.value.value is not None: |
436 |
self.add_message('return-arg-in-generator', node=node, |
437 |
line=retnode.fromlineno) |
438 |
# Check for duplicate names
|
439 |
args = set()
|
440 |
for name in node.argnames(): |
441 |
if name in args: |
442 |
self.add_message('duplicate-argument-name', node=node, args=(name,)) |
443 |
else:
|
444 |
args.add(name) |
445 |
|
446 |
visit_asyncfunctiondef = visit_functiondef |
447 |
|
448 |
def _check_nonlocal_and_global(self, node): |
449 |
"""Check that a name is both nonlocal and global."""
|
450 |
def same_scope(current): |
451 |
return current.scope() is node |
452 |
|
453 |
from_iter = itertools.chain.from_iterable |
454 |
nonlocals = set(from_iter(
|
455 |
child.names for child in node.nodes_of_class(astroid.Nonlocal) |
456 |
if same_scope(child)))
|
457 |
global_vars = set(from_iter(
|
458 |
child.names for child in node.nodes_of_class(astroid.Global) |
459 |
if same_scope(child)))
|
460 |
for name in nonlocals.intersection(global_vars): |
461 |
self.add_message('nonlocal-and-global', |
462 |
args=(name, ), node=node) |
463 |
|
464 |
@check_messages('return-outside-function') |
465 |
def visit_return(self, node): |
466 |
if not isinstance(node.frame(), astroid.FunctionDef): |
467 |
self.add_message('return-outside-function', node=node) |
468 |
|
469 |
@check_messages('yield-outside-function') |
470 |
def visit_yield(self, node): |
471 |
self._check_yield_outside_func(node)
|
472 |
|
473 |
@check_messages('yield-outside-function') |
474 |
def visit_yieldfrom(self, node): |
475 |
self._check_yield_outside_func(node)
|
476 |
|
477 |
@check_messages('not-in-loop', 'continue-in-finally') |
478 |
def visit_continue(self, node): |
479 |
self._check_in_loop(node, 'continue') |
480 |
|
481 |
@check_messages('not-in-loop') |
482 |
def visit_break(self, node): |
483 |
self._check_in_loop(node, 'break') |
484 |
|
485 |
@check_messages('useless-else-on-loop') |
486 |
def visit_for(self, node): |
487 |
self._check_else_on_loop(node)
|
488 |
|
489 |
@check_messages('useless-else-on-loop') |
490 |
def visit_while(self, node): |
491 |
self._check_else_on_loop(node)
|
492 |
|
493 |
@check_messages('nonexistent-operator') |
494 |
def visit_unaryop(self, node): |
495 |
"""check use of the non-existent ++ and -- operator operator"""
|
496 |
if ((node.op in '+-') and |
497 |
isinstance(node.operand, astroid.UnaryOp) and |
498 |
(node.operand.op == node.op)): |
499 |
self.add_message('nonexistent-operator', node=node, args=node.op*2) |
500 |
|
501 |
def _check_nonlocal_without_binding(self, node, name): |
502 |
current_scope = node.scope() |
503 |
while True: |
504 |
if current_scope.parent is None: |
505 |
break
|
506 |
|
507 |
if not isinstance(current_scope, astroid.FunctionDef): |
508 |
self.add_message('nonlocal-without-binding', args=(name, ), |
509 |
node=node) |
510 |
return
|
511 |
else:
|
512 |
if name not in current_scope.locals: |
513 |
current_scope = current_scope.parent.scope() |
514 |
continue
|
515 |
else:
|
516 |
# Okay, found it.
|
517 |
return
|
518 |
|
519 |
self.add_message('nonlocal-without-binding', args=(name, ), node=node) |
520 |
|
521 |
@check_messages('nonlocal-without-binding') |
522 |
def visit_nonlocal(self, node): |
523 |
for name in node.names: |
524 |
self._check_nonlocal_without_binding(node, name)
|
525 |
|
526 |
@check_messages('abstract-class-instantiated') |
527 |
def visit_call(self, node): |
528 |
""" Check instantiating abstract class with
|
529 |
abc.ABCMeta as metaclass.
|
530 |
"""
|
531 |
try:
|
532 |
infered = next(node.func.infer())
|
533 |
except astroid.InferenceError:
|
534 |
return
|
535 |
|
536 |
if not isinstance(infered, astroid.ClassDef): |
537 |
return
|
538 |
|
539 |
klass = node_frame_class(node) |
540 |
if klass is infered: |
541 |
# Don't emit the warning if the class is instantiated
|
542 |
# in its own body or if the call is not an instance
|
543 |
# creation. If the class is instantiated into its own
|
544 |
# body, we're expecting that it knows what it is doing.
|
545 |
return
|
546 |
|
547 |
# __init__ was called
|
548 |
metaclass = infered.metaclass() |
549 |
abstract_methods = _has_abstract_methods(infered) |
550 |
if metaclass is None: |
551 |
# Python 3.4 has `abc.ABC`, which won't be detected
|
552 |
# by ClassNode.metaclass()
|
553 |
for ancestor in infered.ancestors(): |
554 |
if ancestor.qname() == 'abc.ABC' and abstract_methods: |
555 |
self.add_message('abstract-class-instantiated', |
556 |
args=(infered.name, ), |
557 |
node=node) |
558 |
break
|
559 |
return
|
560 |
if metaclass.qname() == 'abc.ABCMeta' and abstract_methods: |
561 |
self.add_message('abstract-class-instantiated', |
562 |
args=(infered.name, ), |
563 |
node=node) |
564 |
|
565 |
def _check_yield_outside_func(self, node): |
566 |
if not isinstance(node.frame(), (astroid.FunctionDef, astroid.Lambda)): |
567 |
self.add_message('yield-outside-function', node=node) |
568 |
|
569 |
def _check_else_on_loop(self, node): |
570 |
"""Check that any loop with an else clause has a break statement."""
|
571 |
if node.orelse and not _loop_exits_early(node): |
572 |
self.add_message('useless-else-on-loop', node=node, |
573 |
# This is not optimal, but the line previous
|
574 |
# to the first statement in the else clause
|
575 |
# will usually be the one that contains the else:.
|
576 |
line=node.orelse[0].lineno - 1) |
577 |
|
578 |
def _check_in_loop(self, node, node_name): |
579 |
"""check that a node is inside a for or while loop"""
|
580 |
_node = node.parent |
581 |
while _node:
|
582 |
if isinstance(_node, (astroid.For, astroid.While)): |
583 |
if node not in _node.orelse: |
584 |
return
|
585 |
|
586 |
if isinstance(_node, (astroid.ClassDef, astroid.FunctionDef)): |
587 |
break
|
588 |
if (isinstance(_node, astroid.TryFinally) |
589 |
and node in _node.finalbody |
590 |
and isinstance(node, astroid.Continue)): |
591 |
self.add_message('continue-in-finally', node=node) |
592 |
|
593 |
_node = _node.parent |
594 |
|
595 |
self.add_message('not-in-loop', node=node, args=node_name) |
596 |
|
597 |
def _check_redefinition(self, redeftype, node): |
598 |
"""check for redefinition of a function / method / class name"""
|
599 |
defined_self = node.parent.frame()[node.name] |
600 |
if defined_self is not node and not are_exclusive(node, defined_self): |
601 |
self.add_message('function-redefined', node=node, |
602 |
args=(redeftype, defined_self.fromlineno)) |
603 |
|
604 |
|
605 |
|
606 |
class BasicChecker(_BasicChecker): |
607 |
"""checks for :
|
608 |
* doc strings
|
609 |
* number of arguments, local variables, branches, returns and statements in
|
610 |
functions, methods
|
611 |
* required module attributes
|
612 |
* dangerous default values as arguments
|
613 |
* redefinition of function / method / class
|
614 |
* uses of the global statement
|
615 |
"""
|
616 |
|
617 |
__implements__ = IAstroidChecker |
618 |
|
619 |
name = 'basic'
|
620 |
msgs = { |
621 |
'W0101': ('Unreachable code', |
622 |
'unreachable',
|
623 |
'Used when there is some code behind a "return" or "raise" '
|
624 |
'statement, which will never be accessed.'),
|
625 |
'W0102': ('Dangerous default value %s as argument', |
626 |
'dangerous-default-value',
|
627 |
'Used when a mutable value as list or dictionary is detected in '
|
628 |
'a default value for an argument.'),
|
629 |
'W0104': ('Statement seems to have no effect', |
630 |
'pointless-statement',
|
631 |
'Used when a statement doesn\'t have (or at least seems to) '
|
632 |
'any effect.'),
|
633 |
'W0105': ('String statement has no effect', |
634 |
'pointless-string-statement',
|
635 |
'Used when a string is used as a statement (which of course '
|
636 |
'has no effect). This is a particular case of W0104 with its '
|
637 |
'own message so you can easily disable it if you\'re using '
|
638 |
'those strings as documentation, instead of comments.'),
|
639 |
'W0106': ('Expression "%s" is assigned to nothing', |
640 |
'expression-not-assigned',
|
641 |
'Used when an expression that is not a function call is assigned '
|
642 |
'to nothing. Probably something else was intended.'),
|
643 |
'W0108': ('Lambda may not be necessary', |
644 |
'unnecessary-lambda',
|
645 |
'Used when the body of a lambda expression is a function call '
|
646 |
'on the same argument list as the lambda itself; such lambda '
|
647 |
'expressions are in all but a few cases replaceable with the '
|
648 |
'function being called in the body of the lambda.'),
|
649 |
'W0109': ("Duplicate key %r in dictionary", |
650 |
'duplicate-key',
|
651 |
'Used when a dictionary expression binds the same key multiple '
|
652 |
'times.'),
|
653 |
'W0122': ('Use of exec', |
654 |
'exec-used',
|
655 |
'Used when you use the "exec" statement (function for Python '
|
656 |
'3), to discourage its usage. That doesn\'t '
|
657 |
'mean you can not use it !'),
|
658 |
'W0123': ('Use of eval', |
659 |
'eval-used',
|
660 |
'Used when you use the "eval" function, to discourage its '
|
661 |
'usage. Consider using `ast.literal_eval` for safely evaluating '
|
662 |
'strings containing Python expressions '
|
663 |
'from untrusted sources. '),
|
664 |
'W0141': ('Used builtin function %s', |
665 |
'bad-builtin',
|
666 |
'Used when a black listed builtin function is used (see the '
|
667 |
'bad-function option). Usual black listed functions are the ones '
|
668 |
'like map, or filter , where Python offers now some cleaner '
|
669 |
'alternative like list comprehension.'),
|
670 |
'W0150': ("%s statement in finally block may swallow exception", |
671 |
'lost-exception',
|
672 |
'Used when a break or a return statement is found inside the '
|
673 |
'finally clause of a try...finally block: the exceptions raised '
|
674 |
'in the try clause will be silently swallowed instead of being '
|
675 |
're-raised.'),
|
676 |
'W0199': ('Assert called on a 2-uple. Did you mean \'assert x,y\'?', |
677 |
'assert-on-tuple',
|
678 |
'A call of assert on a tuple will always evaluate to true if '
|
679 |
'the tuple is not empty, and will always evaluate to false if '
|
680 |
'it is.'),
|
681 |
'W0124': ('Following "as" with another context manager looks like a tuple.', |
682 |
'confusing-with-statement',
|
683 |
'Emitted when a `with` statement component returns multiple values '
|
684 |
'and uses name binding with `as` only for a part of those values, '
|
685 |
'as in with ctx() as a, b. This can be misleading, since it\'s not '
|
686 |
'clear if the context manager returns a tuple or if the node without '
|
687 |
'a name binding is another context manager.'),
|
688 |
'W0125': ('Using a conditional statement with a constant value', |
689 |
'using-constant-test',
|
690 |
'Emitted when a conditional statement (If or ternary if) '
|
691 |
'uses a constant value for its test. This might not be what '
|
692 |
'the user intended to do.'),
|
693 |
'E0111': ('The first reversed() argument is not a sequence', |
694 |
'bad-reversed-sequence',
|
695 |
'Used when the first argument to reversed() builtin '
|
696 |
'isn\'t a sequence (does not implement __reversed__, '
|
697 |
'nor __getitem__ and __len__'),
|
698 |
|
699 |
} |
700 |
|
701 |
options = (('required-attributes',
|
702 |
deprecated_option(opt_type='csv',
|
703 |
help_msg="Required attributes for module. "
|
704 |
"This option is obsolete.")),
|
705 |
|
706 |
('bad-functions',
|
707 |
{'default' : BAD_FUNCTIONS,
|
708 |
'type' :'csv', 'metavar' : '<builtin function names>', |
709 |
'help' : 'List of builtins function names that should not be ' |
710 |
'used, separated by a comma'}
|
711 |
), |
712 |
) |
713 |
reports = (('RP0101', 'Statistics by type', report_by_type_stats),) |
714 |
|
715 |
def __init__(self, linter): |
716 |
_BasicChecker.__init__(self, linter)
|
717 |
self.stats = None |
718 |
self._tryfinallys = None |
719 |
|
720 |
def open(self): |
721 |
"""initialize visit variables and statistics
|
722 |
"""
|
723 |
self._tryfinallys = []
|
724 |
self.stats = self.linter.add_stats(module=0, function=0, |
725 |
method=0, class_=0) |
726 |
|
727 |
@check_messages('using-constant-test') |
728 |
def visit_if(self, node): |
729 |
self._check_using_constant_test(node, node.test)
|
730 |
|
731 |
@check_messages('using-constant-test') |
732 |
def visit_ifexp(self, node): |
733 |
self._check_using_constant_test(node, node.test)
|
734 |
|
735 |
@check_messages('using-constant-test') |
736 |
def visit_comprehension(self, node): |
737 |
if node.ifs:
|
738 |
for if_test in node.ifs: |
739 |
self._check_using_constant_test(node, if_test)
|
740 |
|
741 |
def _check_using_constant_test(self, node, test): |
742 |
const_nodes = ( |
743 |
astroid.Module, |
744 |
astroid.scoped_nodes.GeneratorExp, |
745 |
astroid.Lambda, astroid.FunctionDef, astroid.ClassDef, |
746 |
astroid.bases.Generator, astroid.UnboundMethod, |
747 |
astroid.BoundMethod, astroid.Module) |
748 |
structs = (astroid.Dict, astroid.Tuple, astroid.Set) |
749 |
|
750 |
# These nodes are excepted, since they are not constant
|
751 |
# values, requiring a computation to happen. The only type
|
752 |
# of node in this list which doesn't have this property is
|
753 |
# Getattr, which is excepted because the conditional statement
|
754 |
# can be used to verify that the attribute was set inside a class,
|
755 |
# which is definitely a valid use case.
|
756 |
except_nodes = (astroid.Attribute, astroid.Call, |
757 |
astroid.BinOp, astroid.BoolOp, astroid.UnaryOp, |
758 |
astroid.Subscript) |
759 |
inferred = None
|
760 |
emit = isinstance(test, (astroid.Const, ) + structs + const_nodes)
|
761 |
if not isinstance(test, except_nodes): |
762 |
inferred = safe_infer(test) |
763 |
|
764 |
if emit or isinstance(inferred, const_nodes): |
765 |
self.add_message('using-constant-test', node=node) |
766 |
|
767 |
def visit_module(self, _): |
768 |
"""check module name, docstring and required arguments
|
769 |
"""
|
770 |
self.stats['module'] += 1 |
771 |
|
772 |
def visit_classdef(self, node): # pylint: disable=unused-argument |
773 |
"""check module name, docstring and redefinition
|
774 |
increment branch counter
|
775 |
"""
|
776 |
self.stats['class'] += 1 |
777 |
|
778 |
@check_messages('pointless-statement', 'pointless-string-statement', |
779 |
'expression-not-assigned')
|
780 |
def visit_expr(self, node): |
781 |
"""check for various kind of statements without effect"""
|
782 |
expr = node.value |
783 |
if isinstance(expr, astroid.Const) and isinstance(expr.value, |
784 |
six.string_types): |
785 |
# treat string statement in a separated message
|
786 |
# Handle PEP-257 attribute docstrings.
|
787 |
# An attribute docstring is defined as being a string right after
|
788 |
# an assignment at the module level, class level or __init__ level.
|
789 |
scope = expr.scope() |
790 |
if isinstance(scope, (astroid.ClassDef, astroid.Module, astroid.FunctionDef)): |
791 |
if isinstance(scope, astroid.FunctionDef) and scope.name != '__init__': |
792 |
pass
|
793 |
else:
|
794 |
sibling = expr.previous_sibling() |
795 |
if (sibling is not None and sibling.scope() is scope and |
796 |
isinstance(sibling, astroid.Assign)):
|
797 |
return
|
798 |
self.add_message('pointless-string-statement', node=node) |
799 |
return
|
800 |
# ignore if this is :
|
801 |
# * a direct function call
|
802 |
# * the unique child of a try/except body
|
803 |
# * a yield (which are wrapped by a discard node in _ast XXX)
|
804 |
# warn W0106 if we have any underlying function call (we can't predict
|
805 |
# side effects), else pointless-statement
|
806 |
if (isinstance(expr, (astroid.Yield, astroid.Await, astroid.Call)) or |
807 |
(isinstance(node.parent, astroid.TryExcept) and |
808 |
node.parent.body == [node])): |
809 |
return
|
810 |
if any(expr.nodes_of_class(astroid.Call)): |
811 |
self.add_message('expression-not-assigned', node=node, |
812 |
args=expr.as_string()) |
813 |
else:
|
814 |
self.add_message('pointless-statement', node=node) |
815 |
|
816 |
@staticmethod
|
817 |
def _filter_vararg(node, call_args): |
818 |
# Return the arguments for the given call which are
|
819 |
# not passed as vararg.
|
820 |
for arg in call_args: |
821 |
if isinstance(arg, astroid.Starred): |
822 |
if (isinstance(arg.value, astroid.Name) |
823 |
and arg.value.name != node.args.vararg):
|
824 |
yield arg
|
825 |
else:
|
826 |
yield arg
|
827 |
|
828 |
@staticmethod
|
829 |
def _has_variadic_argument(args, variadic_name): |
830 |
if not args: |
831 |
return True |
832 |
for arg in args: |
833 |
if isinstance(arg.value, astroid.Name): |
834 |
if arg.value.name != variadic_name:
|
835 |
return True |
836 |
else:
|
837 |
return True |
838 |
return False |
839 |
|
840 |
@check_messages('unnecessary-lambda') |
841 |
def visit_lambda(self, node): |
842 |
"""check whether or not the lambda is suspicious
|
843 |
"""
|
844 |
# if the body of the lambda is a call expression with the same
|
845 |
# argument list as the lambda itself, then the lambda is
|
846 |
# possibly unnecessary and at least suspicious.
|
847 |
if node.args.defaults:
|
848 |
# If the arguments of the lambda include defaults, then a
|
849 |
# judgment cannot be made because there is no way to check
|
850 |
# that the defaults defined by the lambda are the same as
|
851 |
# the defaults defined by the function called in the body
|
852 |
# of the lambda.
|
853 |
return
|
854 |
call = node.body |
855 |
if not isinstance(call, astroid.Call): |
856 |
# The body of the lambda must be a function call expression
|
857 |
# for the lambda to be unnecessary.
|
858 |
return
|
859 |
if (isinstance(node.body.func, astroid.Attribute) and |
860 |
isinstance(node.body.func.expr, astroid.Call)):
|
861 |
# Chained call, the intermediate call might
|
862 |
# return something else (but we don't check that, yet).
|
863 |
return
|
864 |
|
865 |
ordinary_args = list(node.args.args)
|
866 |
new_call_args = list(self._filter_vararg(node, call.args)) |
867 |
if node.args.kwarg:
|
868 |
if self._has_variadic_argument(call.kwargs, node.args.kwarg): |
869 |
return
|
870 |
elif call.kwargs or call.keywords: |
871 |
return
|
872 |
|
873 |
if node.args.vararg:
|
874 |
if self._has_variadic_argument(call.starargs, node.args.vararg): |
875 |
return
|
876 |
elif call.starargs:
|
877 |
return
|
878 |
|
879 |
# The "ordinary" arguments must be in a correspondence such that:
|
880 |
# ordinary_args[i].name == call.args[i].name.
|
881 |
if len(ordinary_args) != len(new_call_args): |
882 |
return
|
883 |
for arg, passed_arg in zip(ordinary_args, new_call_args): |
884 |
if not isinstance(passed_arg, astroid.Name): |
885 |
return
|
886 |
if arg.name != passed_arg.name:
|
887 |
return
|
888 |
|
889 |
self.add_message('unnecessary-lambda', line=node.fromlineno, |
890 |
node=node) |
891 |
|
892 |
@check_messages('dangerous-default-value') |
893 |
def visit_functiondef(self, node): |
894 |
"""check function name, docstring, arguments, redefinition,
|
895 |
variable names, max locals
|
896 |
"""
|
897 |
self.stats[node.is_method() and 'method' or 'function'] += 1 |
898 |
self._check_dangerous_default(node)
|
899 |
|
900 |
visit_asyncfunctiondef = visit_functiondef |
901 |
|
902 |
def _check_dangerous_default(self, node): |
903 |
# check for dangerous default values as arguments
|
904 |
is_iterable = lambda n: isinstance(n, (astroid.List, |
905 |
astroid.Set, |
906 |
astroid.Dict)) |
907 |
for default in node.args.defaults: |
908 |
try:
|
909 |
value = next(default.infer())
|
910 |
except astroid.InferenceError:
|
911 |
continue
|
912 |
|
913 |
if (isinstance(value, astroid.Instance) and |
914 |
value.qname() in DEFAULT_ARGUMENT_SYMBOLS):
|
915 |
|
916 |
if value is default: |
917 |
msg = DEFAULT_ARGUMENT_SYMBOLS[value.qname()] |
918 |
elif isinstance(value, astroid.Instance) or is_iterable(value): |
919 |
# We are here in the following situation(s):
|
920 |
# * a dict/set/list/tuple call which wasn't inferred
|
921 |
# to a syntax node ({}, () etc.). This can happen
|
922 |
# when the arguments are invalid or unknown to
|
923 |
# the inference.
|
924 |
# * a variable from somewhere else, which turns out to be a list
|
925 |
# or a dict.
|
926 |
if is_iterable(default):
|
927 |
msg = value.pytype() |
928 |
elif isinstance(default, astroid.Call): |
929 |
msg = '%s() (%s)' % (value.name, value.qname())
|
930 |
else:
|
931 |
msg = '%s (%s)' % (default.as_string(), value.qname())
|
932 |
else:
|
933 |
# this argument is a name
|
934 |
msg = '%s (%s)' % (default.as_string(),
|
935 |
DEFAULT_ARGUMENT_SYMBOLS[value.qname()]) |
936 |
self.add_message('dangerous-default-value', |
937 |
node=node, |
938 |
args=(msg, )) |
939 |
|
940 |
@check_messages('unreachable', 'lost-exception') |
941 |
def visit_return(self, node): |
942 |
"""1 - check is the node has a right sibling (if so, that's some
|
943 |
unreachable code)
|
944 |
2 - check is the node is inside the finally clause of a try...finally
|
945 |
block
|
946 |
"""
|
947 |
self._check_unreachable(node)
|
948 |
# Is it inside final body of a try...finally bloc ?
|
949 |
self._check_not_in_finally(node, 'return', (astroid.FunctionDef,)) |
950 |
|
951 |
@check_messages('unreachable') |
952 |
def visit_continue(self, node): |
953 |
"""check is the node has a right sibling (if so, that's some unreachable
|
954 |
code)
|
955 |
"""
|
956 |
self._check_unreachable(node)
|
957 |
|
958 |
@check_messages('unreachable', 'lost-exception') |
959 |
def visit_break(self, node): |
960 |
"""1 - check is the node has a right sibling (if so, that's some
|
961 |
unreachable code)
|
962 |
2 - check is the node is inside the finally clause of a try...finally
|
963 |
block
|
964 |
"""
|
965 |
# 1 - Is it right sibling ?
|
966 |
self._check_unreachable(node)
|
967 |
# 2 - Is it inside final body of a try...finally bloc ?
|
968 |
self._check_not_in_finally(node, 'break', (astroid.For, astroid.While,)) |
969 |
|
970 |
@check_messages('unreachable') |
971 |
def visit_raise(self, node): |
972 |
"""check if the node has a right sibling (if so, that's some unreachable
|
973 |
code)
|
974 |
"""
|
975 |
self._check_unreachable(node)
|
976 |
|
977 |
@check_messages('exec-used') |
978 |
def visit_exec(self, node): |
979 |
"""just print a warning on exec statements"""
|
980 |
self.add_message('exec-used', node=node) |
981 |
|
982 |
@check_messages('bad-builtin', 'eval-used', |
983 |
'exec-used', 'bad-reversed-sequence') |
984 |
def visit_call(self, node): |
985 |
"""visit a CallFunc node -> check if this is not a blacklisted builtin
|
986 |
call and check for * or ** use
|
987 |
"""
|
988 |
if isinstance(node.func, astroid.Name): |
989 |
name = node.func.name |
990 |
# ignore the name if it's not a builtin (i.e. not defined in the
|
991 |
# locals nor globals scope)
|
992 |
if not (name in node.frame() or |
993 |
name in node.root()):
|
994 |
if name == 'exec': |
995 |
self.add_message('exec-used', node=node) |
996 |
elif name == 'reversed': |
997 |
self._check_reversed(node)
|
998 |
elif name == 'eval': |
999 |
self.add_message('eval-used', node=node) |
1000 |
if name in self.config.bad_functions: |
1001 |
hint = BUILTIN_HINTS.get(name) |
1002 |
if hint:
|
1003 |
args = "%r. %s" % (name, hint)
|
1004 |
else:
|
1005 |
args = repr(name)
|
1006 |
self.add_message('bad-builtin', node=node, args=args) |
1007 |
|
1008 |
@check_messages('assert-on-tuple') |
1009 |
def visit_assert(self, node): |
1010 |
"""check the use of an assert statement on a tuple."""
|
1011 |
if node.fail is None and isinstance(node.test, astroid.Tuple) and \ |
1012 |
len(node.test.elts) == 2: |
1013 |
self.add_message('assert-on-tuple', node=node) |
1014 |
|
1015 |
@check_messages('duplicate-key') |
1016 |
def visit_dict(self, node): |
1017 |
"""check duplicate key in dictionary"""
|
1018 |
keys = set()
|
1019 |
for k, _ in node.items: |
1020 |
if isinstance(k, astroid.Const): |
1021 |
key = k.value |
1022 |
if key in keys: |
1023 |
self.add_message('duplicate-key', node=node, args=key) |
1024 |
keys.add(key) |
1025 |
|
1026 |
def visit_tryfinally(self, node): |
1027 |
"""update try...finally flag"""
|
1028 |
self._tryfinallys.append(node)
|
1029 |
|
1030 |
def leave_tryfinally(self, node): # pylint: disable=unused-argument |
1031 |
"""update try...finally flag"""
|
1032 |
self._tryfinallys.pop()
|
1033 |
|
1034 |
def _check_unreachable(self, node): |
1035 |
"""check unreachable code"""
|
1036 |
unreach_stmt = node.next_sibling() |
1037 |
if unreach_stmt is not None: |
1038 |
self.add_message('unreachable', node=unreach_stmt) |
1039 |
|
1040 |
def _check_not_in_finally(self, node, node_name, breaker_classes=()): |
1041 |
"""check that a node is not inside a finally clause of a
|
1042 |
try...finally statement.
|
1043 |
If we found before a try...finally bloc a parent which its type is
|
1044 |
in breaker_classes, we skip the whole check."""
|
1045 |
# if self._tryfinallys is empty, we're not a in try...finally bloc
|
1046 |
if not self._tryfinallys: |
1047 |
return
|
1048 |
# the node could be a grand-grand...-children of the try...finally
|
1049 |
_parent = node.parent |
1050 |
_node = node |
1051 |
while _parent and not isinstance(_parent, breaker_classes): |
1052 |
if hasattr(_parent, 'finalbody') and _node in _parent.finalbody: |
1053 |
self.add_message('lost-exception', node=node, args=node_name) |
1054 |
return
|
1055 |
_node = _parent |
1056 |
_parent = _node.parent |
1057 |
|
1058 |
def _check_reversed(self, node): |
1059 |
""" check that the argument to `reversed` is a sequence """
|
1060 |
try:
|
1061 |
argument = safe_infer(get_argument_from_call(node, position=0))
|
1062 |
except NoSuchArgumentError:
|
1063 |
pass
|
1064 |
else:
|
1065 |
if argument is astroid.YES: |
1066 |
return
|
1067 |
if argument is None: |
1068 |
# Nothing was infered.
|
1069 |
# Try to see if we have iter().
|
1070 |
if isinstance(node.args[0], astroid.Call): |
1071 |
try:
|
1072 |
func = next(node.args[0].func.infer()) |
1073 |
except InferenceError:
|
1074 |
return
|
1075 |
if (getattr(func, 'name', None) == 'iter' and |
1076 |
is_builtin_object(func)): |
1077 |
self.add_message('bad-reversed-sequence', node=node) |
1078 |
return
|
1079 |
|
1080 |
if isinstance(argument, astroid.Instance): |
1081 |
if (argument._proxied.name == 'dict' and |
1082 |
is_builtin_object(argument._proxied)): |
1083 |
self.add_message('bad-reversed-sequence', node=node) |
1084 |
return
|
1085 |
elif any(ancestor.name == 'dict' and is_builtin_object(ancestor) |
1086 |
for ancestor in argument._proxied.ancestors()): |
1087 |
# Mappings aren't accepted by reversed(), unless
|
1088 |
# they provide explicitly a __reversed__ method.
|
1089 |
try:
|
1090 |
argument.locals[REVERSED_PROTOCOL_METHOD] |
1091 |
except KeyError: |
1092 |
self.add_message('bad-reversed-sequence', node=node) |
1093 |
return
|
1094 |
|
1095 |
for methods in REVERSED_METHODS: |
1096 |
for meth in methods: |
1097 |
try:
|
1098 |
argument.getattr(meth) |
1099 |
except astroid.NotFoundError:
|
1100 |
break
|
1101 |
else:
|
1102 |
break
|
1103 |
else:
|
1104 |
self.add_message('bad-reversed-sequence', node=node) |
1105 |
elif not isinstance(argument, (astroid.List, astroid.Tuple)): |
1106 |
# everything else is not a proper sequence for reversed()
|
1107 |
self.add_message('bad-reversed-sequence', node=node) |
1108 |
|
1109 |
@check_messages('confusing-with-statement') |
1110 |
def visit_with(self, node): |
1111 |
if not PY3K: |
1112 |
# in Python 2 a "with" statement with multiple managers coresponds
|
1113 |
# to multiple nested AST "With" nodes
|
1114 |
pairs = [] |
1115 |
parent_node = node.parent |
1116 |
if isinstance(parent_node, astroid.With): |
1117 |
# we only care about the direct parent, since this method
|
1118 |
# gets called for each with node anyway
|
1119 |
pairs.extend(parent_node.items) |
1120 |
pairs.extend(node.items) |
1121 |
else:
|
1122 |
# in PY3K a "with" statement with multiple managers coresponds
|
1123 |
# to one AST "With" node with multiple items
|
1124 |
pairs = node.items |
1125 |
if pairs:
|
1126 |
for prev_pair, pair in zip(pairs, pairs[1:]): |
1127 |
if (isinstance(prev_pair[1], astroid.AssignName) and |
1128 |
(pair[1] is None and not isinstance(pair[0], astroid.Call))): |
1129 |
# don't emit a message if the second is a function call
|
1130 |
# there's no way that can be mistaken for a name assignment
|
1131 |
if PY3K or node.lineno == node.parent.lineno: |
1132 |
# if the line number doesn't match
|
1133 |
# we assume it's a nested "with"
|
1134 |
self.add_message('confusing-with-statement', node=node) |
1135 |
|
1136 |
|
1137 |
_NAME_TYPES = { |
1138 |
'module': (MOD_NAME_RGX, 'module'), |
1139 |
'const': (CONST_NAME_RGX, 'constant'), |
1140 |
'class': (CLASS_NAME_RGX, 'class'), |
1141 |
'function': (DEFAULT_NAME_RGX, 'function'), |
1142 |
'method': (DEFAULT_NAME_RGX, 'method'), |
1143 |
'attr': (DEFAULT_NAME_RGX, 'attribute'), |
1144 |
'argument': (DEFAULT_NAME_RGX, 'argument'), |
1145 |
'variable': (DEFAULT_NAME_RGX, 'variable'), |
1146 |
'class_attribute': (CLASS_ATTRIBUTE_RGX, 'class attribute'), |
1147 |
'inlinevar': (COMP_VAR_RGX, 'inline iteration'), |
1148 |
} |
1149 |
|
1150 |
def _create_naming_options(): |
1151 |
name_options = [] |
1152 |
for name_type, (rgx, human_readable_name) in six.iteritems(_NAME_TYPES): |
1153 |
name_type = name_type.replace('_', '-') |
1154 |
name_options.append(( |
1155 |
'%s-rgx' % (name_type,),
|
1156 |
{'default': rgx, 'type': 'regexp', 'metavar': '<regexp>', |
1157 |
'help': 'Regular expression matching correct %s names' % (human_readable_name,)})) |
1158 |
name_options.append(( |
1159 |
'%s-name-hint' % (name_type,),
|
1160 |
{'default': rgx.pattern, 'type': 'string', 'metavar': '<string>', |
1161 |
'help': 'Naming hint for %s names' % (human_readable_name,)})) |
1162 |
return tuple(name_options) |
1163 |
|
1164 |
class NameChecker(_BasicChecker): |
1165 |
msgs = { |
1166 |
'C0102': ('Black listed name "%s"', |
1167 |
'blacklisted-name',
|
1168 |
'Used when the name is listed in the black list (unauthorized '
|
1169 |
'names).'),
|
1170 |
'C0103': ('Invalid %s name "%s"%s', |
1171 |
'invalid-name',
|
1172 |
'Used when the name doesn\'t match the regular expression '
|
1173 |
'associated to its type (constant, variable, class...).'),
|
1174 |
} |
1175 |
|
1176 |
options = (('good-names',
|
1177 |
{'default' : ('i', 'j', 'k', 'ex', 'Run', '_'), |
1178 |
'type' :'csv', 'metavar' : '<names>', |
1179 |
'help' : 'Good variable names which should always be accepted,' |
1180 |
' separated by a comma'}
|
1181 |
), |
1182 |
('bad-names',
|
1183 |
{'default' : ('foo', 'bar', 'baz', 'toto', 'tutu', 'tata'), |
1184 |
'type' :'csv', 'metavar' : '<names>', |
1185 |
'help' : 'Bad variable names which should always be refused, ' |
1186 |
'separated by a comma'}
|
1187 |
), |
1188 |
('name-group',
|
1189 |
{'default' : (),
|
1190 |
'type' :'csv', 'metavar' : '<name1:name2>', |
1191 |
'help' : ('Colon-delimited sets of names that determine each' |
1192 |
' other\'s naming style when the name regexes'
|
1193 |
' allow several styles.')}
|
1194 |
), |
1195 |
('include-naming-hint',
|
1196 |
{'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>', |
1197 |
'help': 'Include a hint for the correct naming format with invalid-name'} |
1198 |
), |
1199 |
) + _create_naming_options() |
1200 |
|
1201 |
|
1202 |
def __init__(self, linter): |
1203 |
_BasicChecker.__init__(self, linter)
|
1204 |
self._name_category = {}
|
1205 |
self._name_group = {}
|
1206 |
self._bad_names = {}
|
1207 |
|
1208 |
def open(self): |
1209 |
self.stats = self.linter.add_stats(badname_module=0, |
1210 |
badname_class=0, badname_function=0, |
1211 |
badname_method=0, badname_attr=0, |
1212 |
badname_const=0,
|
1213 |
badname_variable=0,
|
1214 |
badname_inlinevar=0,
|
1215 |
badname_argument=0,
|
1216 |
badname_class_attribute=0)
|
1217 |
for group in self.config.name_group: |
1218 |
for name_type in group.split(':'): |
1219 |
self._name_group[name_type] = 'group_%s' % (group,) |
1220 |
|
1221 |
@check_messages('blacklisted-name', 'invalid-name') |
1222 |
def visit_module(self, node): |
1223 |
self._check_name('module', node.name.split('.')[-1], node) |
1224 |
self._bad_names = {}
|
1225 |
|
1226 |
def leave_module(self, node): # pylint: disable=unused-argument |
1227 |
for all_groups in six.itervalues(self._bad_names): |
1228 |
if len(all_groups) < 2: |
1229 |
continue
|
1230 |
groups = collections.defaultdict(list)
|
1231 |
min_warnings = sys.maxsize |
1232 |
for group in six.itervalues(all_groups): |
1233 |
groups[len(group)].append(group)
|
1234 |
min_warnings = min(len(group), min_warnings) |
1235 |
if len(groups[min_warnings]) > 1: |
1236 |
by_line = sorted(groups[min_warnings],
|
1237 |
key=lambda group: min(warning[0].lineno for warning in group)) |
1238 |
warnings = itertools.chain(*by_line[1:])
|
1239 |
else:
|
1240 |
warnings = groups[min_warnings][0]
|
1241 |
for args in warnings: |
1242 |
self._raise_name_warning(*args)
|
1243 |
|
1244 |
@check_messages('blacklisted-name', 'invalid-name') |
1245 |
def visit_classdef(self, node): |
1246 |
self._check_name('class', node.name, node) |
1247 |
for attr, anodes in six.iteritems(node.instance_attrs): |
1248 |
if not any(node.instance_attr_ancestors(attr)): |
1249 |
self._check_name('attr', attr, anodes[0]) |
1250 |
|
1251 |
@check_messages('blacklisted-name', 'invalid-name') |
1252 |
def visit_functiondef(self, node): |
1253 |
# Do not emit any warnings if the method is just an implementation
|
1254 |
# of a base class method.
|
1255 |
confidence = HIGH |
1256 |
if node.is_method():
|
1257 |
if overrides_a_method(node.parent.frame(), node.name):
|
1258 |
return
|
1259 |
confidence = (INFERENCE if has_known_bases(node.parent.frame())
|
1260 |
else INFERENCE_FAILURE)
|
1261 |
|
1262 |
self._check_name(_determine_function_name_type(node),
|
1263 |
node.name, node, confidence) |
1264 |
# Check argument names
|
1265 |
args = node.args.args |
1266 |
if args is not None: |
1267 |
self._recursive_check_names(args, node)
|
1268 |
|
1269 |
visit_asyncfunctiondef = visit_functiondef |
1270 |
|
1271 |
@check_messages('blacklisted-name', 'invalid-name') |
1272 |
def visit_global(self, node): |
1273 |
for name in node.names: |
1274 |
self._check_name('const', name, node) |
1275 |
|
1276 |
@check_messages('blacklisted-name', 'invalid-name') |
1277 |
def visit_assignname(self, node): |
1278 |
"""check module level assigned names"""
|
1279 |
frame = node.frame() |
1280 |
ass_type = node.assign_type() |
1281 |
if isinstance(ass_type, astroid.Comprehension): |
1282 |
self._check_name('inlinevar', node.name, node) |
1283 |
elif isinstance(frame, astroid.Module): |
1284 |
if isinstance(ass_type, astroid.Assign) and not in_loop(ass_type): |
1285 |
if isinstance(safe_infer(ass_type.value), astroid.ClassDef): |
1286 |
self._check_name('class', node.name, node) |
1287 |
else:
|
1288 |
if not _redefines_import(node): |
1289 |
# Don't emit if the name redefines an import
|
1290 |
# in an ImportError except handler.
|
1291 |
self._check_name('const', node.name, node) |
1292 |
elif isinstance(ass_type, astroid.ExceptHandler): |
1293 |
self._check_name('variable', node.name, node) |
1294 |
elif isinstance(frame, astroid.FunctionDef): |
1295 |
# global introduced variable aren't in the function locals
|
1296 |
if node.name in frame and node.name not in frame.argnames(): |
1297 |
if not _redefines_import(node): |
1298 |
self._check_name('variable', node.name, node) |
1299 |
elif isinstance(frame, astroid.ClassDef): |
1300 |
if not list(frame.local_attr_ancestors(node.name)): |
1301 |
self._check_name('class_attribute', node.name, node) |
1302 |
|
1303 |
def _recursive_check_names(self, args, node): |
1304 |
"""check names in a possibly recursive list <arg>"""
|
1305 |
for arg in args: |
1306 |
if isinstance(arg, astroid.AssignName): |
1307 |
self._check_name('argument', arg.name, node) |
1308 |
else:
|
1309 |
self._recursive_check_names(arg.elts, node)
|
1310 |
|
1311 |
def _find_name_group(self, node_type): |
1312 |
return self._name_group.get(node_type, node_type) |
1313 |
|
1314 |
def _raise_name_warning(self, node, node_type, name, confidence): |
1315 |
type_label = _NAME_TYPES[node_type][1]
|
1316 |
hint = ''
|
1317 |
if self.config.include_naming_hint: |
1318 |
hint = ' (hint: %s)' % (getattr(self.config, node_type + '_name_hint')) |
1319 |
self.add_message('invalid-name', node=node, args=(type_label, name, hint), |
1320 |
confidence=confidence) |
1321 |
self.stats['badname_' + node_type] += 1 |
1322 |
|
1323 |
def _check_name(self, node_type, name, node, confidence=HIGH): |
1324 |
"""check for a name using the type's regexp"""
|
1325 |
if is_inside_except(node):
|
1326 |
clobbering, _ = clobber_in_except(node) |
1327 |
if clobbering:
|
1328 |
return
|
1329 |
if name in self.config.good_names: |
1330 |
return
|
1331 |
if name in self.config.bad_names: |
1332 |
self.stats['badname_' + node_type] += 1 |
1333 |
self.add_message('blacklisted-name', node=node, args=name) |
1334 |
return
|
1335 |
regexp = getattr(self.config, node_type + '_rgx') |
1336 |
match = regexp.match(name) |
1337 |
|
1338 |
if _is_multi_naming_match(match, node_type, confidence):
|
1339 |
name_group = self._find_name_group(node_type)
|
1340 |
bad_name_group = self._bad_names.setdefault(name_group, {})
|
1341 |
warnings = bad_name_group.setdefault(match.lastgroup, []) |
1342 |
warnings.append((node, node_type, name, confidence)) |
1343 |
|
1344 |
if match is None: |
1345 |
self._raise_name_warning(node, node_type, name, confidence)
|
1346 |
|
1347 |
|
1348 |
class DocStringChecker(_BasicChecker): |
1349 |
msgs = { |
1350 |
'C0111': ('Missing %s docstring', # W0131 |
1351 |
'missing-docstring',
|
1352 |
'Used when a module, function, class or method has no docstring.'
|
1353 |
'Some special methods like __init__ doesn\'t necessary require a '
|
1354 |
'docstring.'),
|
1355 |
'C0112': ('Empty %s docstring', # W0132 |
1356 |
'empty-docstring',
|
1357 |
'Used when a module, function, class or method has an empty '
|
1358 |
'docstring (it would be too easy ;).'),
|
1359 |
} |
1360 |
options = (('no-docstring-rgx',
|
1361 |
{'default' : NO_REQUIRED_DOC_RGX,
|
1362 |
'type' : 'regexp', 'metavar' : '<regexp>', |
1363 |
'help' : 'Regular expression which should only match ' |
1364 |
'function or class names that do not require a '
|
1365 |
'docstring.'}
|
1366 |
), |
1367 |
('docstring-min-length',
|
1368 |
{'default' : -1, |
1369 |
'type' : 'int', 'metavar' : '<int>', |
1370 |
'help': ('Minimum line length for functions/classes that' |
1371 |
' require docstrings, shorter ones are exempt.')}
|
1372 |
), |
1373 |
) |
1374 |
|
1375 |
|
1376 |
def open(self): |
1377 |
self.stats = self.linter.add_stats(undocumented_module=0, |
1378 |
undocumented_function=0,
|
1379 |
undocumented_method=0,
|
1380 |
undocumented_class=0)
|
1381 |
@check_messages('missing-docstring', 'empty-docstring') |
1382 |
def visit_module(self, node): |
1383 |
self._check_docstring('module', node) |
1384 |
|
1385 |
@check_messages('missing-docstring', 'empty-docstring') |
1386 |
def visit_classdef(self, node): |
1387 |
if self.config.no_docstring_rgx.match(node.name) is None: |
1388 |
self._check_docstring('class', node) |
1389 |
|
1390 |
@staticmethod
|
1391 |
def _is_setter_or_deleter(node): |
1392 |
names = {'setter', 'deleter'} |
1393 |
for decorator in node.decorators.nodes: |
1394 |
if (isinstance(decorator, astroid.Attribute) |
1395 |
and decorator.attrname in names): |
1396 |
return True |
1397 |
return False |
1398 |
|
1399 |
@check_messages('missing-docstring', 'empty-docstring') |
1400 |
def visit_functiondef(self, node): |
1401 |
if self.config.no_docstring_rgx.match(node.name) is None: |
1402 |
ftype = node.is_method() and 'method' or 'function' |
1403 |
if node.decorators and self._is_setter_or_deleter(node): |
1404 |
return
|
1405 |
|
1406 |
if isinstance(node.parent.frame(), astroid.ClassDef): |
1407 |
overridden = False
|
1408 |
confidence = (INFERENCE if has_known_bases(node.parent.frame())
|
1409 |
else INFERENCE_FAILURE)
|
1410 |
# check if node is from a method overridden by its ancestor
|
1411 |
for ancestor in node.parent.frame().ancestors(): |
1412 |
if node.name in ancestor and \ |
1413 |
isinstance(ancestor[node.name], astroid.FunctionDef):
|
1414 |
overridden = True
|
1415 |
break
|
1416 |
self._check_docstring(ftype, node,
|
1417 |
report_missing=not overridden,
|
1418 |
confidence=confidence) |
1419 |
else:
|
1420 |
self._check_docstring(ftype, node)
|
1421 |
|
1422 |
visit_asyncfunctiondef = visit_functiondef |
1423 |
|
1424 |
def _check_docstring(self, node_type, node, report_missing=True, |
1425 |
confidence=HIGH): |
1426 |
"""check the node has a non empty docstring"""
|
1427 |
docstring = node.doc |
1428 |
if docstring is None: |
1429 |
if not report_missing: |
1430 |
return
|
1431 |
if node.body:
|
1432 |
lines = node.body[-1].lineno - node.body[0].lineno + 1 |
1433 |
else:
|
1434 |
lines = 0
|
1435 |
|
1436 |
if node_type == 'module' and not lines: |
1437 |
# If the module has no body, there's no reason
|
1438 |
# to require a docstring.
|
1439 |
return
|
1440 |
max_lines = self.config.docstring_min_length
|
1441 |
|
1442 |
if node_type != 'module' and max_lines > -1 and lines < max_lines: |
1443 |
return
|
1444 |
self.stats['undocumented_'+node_type] += 1 |
1445 |
if (node.body and isinstance(node.body[0], astroid.Expr) and |
1446 |
isinstance(node.body[0].value, astroid.Call)): |
1447 |
# Most likely a string with a format call. Let's see.
|
1448 |
func = safe_infer(node.body[0].value.func)
|
1449 |
if (isinstance(func, astroid.BoundMethod) |
1450 |
and isinstance(func.bound, astroid.Instance)): |
1451 |
# Strings in Python 3, others in Python 2.
|
1452 |
if PY3K and func.bound.name == 'str': |
1453 |
return
|
1454 |
elif func.bound.name in ('str', 'unicode', 'bytes'): |
1455 |
return
|
1456 |
self.add_message('missing-docstring', node=node, args=(node_type,), |
1457 |
confidence=confidence) |
1458 |
elif not docstring.strip(): |
1459 |
self.stats['undocumented_'+node_type] += 1 |
1460 |
self.add_message('empty-docstring', node=node, args=(node_type,), |
1461 |
confidence=confidence) |
1462 |
|
1463 |
|
1464 |
class PassChecker(_BasicChecker): |
1465 |
"""check if the pass statement is really necessary"""
|
1466 |
msgs = {'W0107': ('Unnecessary pass statement', |
1467 |
'unnecessary-pass',
|
1468 |
'Used when a "pass" statement that can be avoided is '
|
1469 |
'encountered.'),
|
1470 |
} |
1471 |
@check_messages('unnecessary-pass') |
1472 |
def visit_pass(self, node): |
1473 |
if len(node.parent.child_sequence(node)) > 1: |
1474 |
self.add_message('unnecessary-pass', node=node) |
1475 |
|
1476 |
|
1477 |
class LambdaForComprehensionChecker(_BasicChecker): |
1478 |
"""check for using a lambda where a comprehension would do.
|
1479 |
|
1480 |
See <http://www.artima.com/weblogs/viewpost.jsp?thread=98196>
|
1481 |
where GvR says comprehensions would be clearer.
|
1482 |
"""
|
1483 |
|
1484 |
msgs = {'W0110': ('map/filter on lambda could be replaced by comprehension', |
1485 |
'deprecated-lambda',
|
1486 |
'Used when a lambda is the first argument to "map" or '
|
1487 |
'"filter". It could be clearer as a list '
|
1488 |
'comprehension or generator expression.',
|
1489 |
{'maxversion': (3, 0)}), |
1490 |
} |
1491 |
|
1492 |
@check_messages('deprecated-lambda') |
1493 |
def visit_call(self, node): |
1494 |
"""visit a CallFunc node, check if map or filter are called with a
|
1495 |
lambda
|
1496 |
"""
|
1497 |
if not node.args: |
1498 |
return
|
1499 |
if not isinstance(node.args[0], astroid.Lambda): |
1500 |
return
|
1501 |
infered = safe_infer(node.func) |
1502 |
if (is_builtin_object(infered)
|
1503 |
and infered.name in ['map', 'filter']): |
1504 |
self.add_message('deprecated-lambda', node=node) |
1505 |
|
1506 |
|
1507 |
class RecommandationChecker(_BasicChecker): |
1508 |
msgs = {'C0200': ('Consider using enumerate instead of iterating with range and len', |
1509 |
'consider-using-enumerate',
|
1510 |
'Emitted when code that iterates with range and len is '
|
1511 |
'encountered. Such code can be simplified by using the '
|
1512 |
'enumerate builtin.'),
|
1513 |
} |
1514 |
|
1515 |
@staticmethod
|
1516 |
def _is_builtin(node, function): |
1517 |
inferred = safe_infer(node) |
1518 |
if not inferred: |
1519 |
return False |
1520 |
return is_builtin_object(inferred) and inferred.name == function |
1521 |
|
1522 |
@check_messages('consider-using-enumerate') |
1523 |
def visit_for(self, node): |
1524 |
"""Emit a convention whenever range and len are used for indexing."""
|
1525 |
# Verify that we have a `range(len(...))` call and that the object
|
1526 |
# which is iterated is used as a subscript in the body of the for.
|
1527 |
|
1528 |
# Is it a proper range call?
|
1529 |
if not isinstance(node.iter, astroid.Call): |
1530 |
return
|
1531 |
if not self._is_builtin(node.iter.func, 'range'): |
1532 |
return
|
1533 |
if len(node.iter.args) != 1: |
1534 |
return
|
1535 |
|
1536 |
# Is it a proper len call?
|
1537 |
if not isinstance(node.iter.args[0], astroid.Call): |
1538 |
return
|
1539 |
second_func = node.iter.args[0].func
|
1540 |
if not self._is_builtin(second_func, 'len'): |
1541 |
return
|
1542 |
len_args = node.iter.args[0].args
|
1543 |
if not len_args or len(len_args) != 1: |
1544 |
return
|
1545 |
iterating_object = len_args[0]
|
1546 |
if not isinstance(iterating_object, astroid.Name): |
1547 |
return
|
1548 |
|
1549 |
# Verify that the body of the for loop uses a subscript
|
1550 |
# with the object that was iterated. This uses some heuristics
|
1551 |
# in order to make sure that the same object is used in the
|
1552 |
# for body.
|
1553 |
for child in node.body: |
1554 |
for subscript in child.nodes_of_class(astroid.Subscript): |
1555 |
if not isinstance(subscript.value, astroid.Name): |
1556 |
continue
|
1557 |
if not isinstance(subscript.slice, astroid.Index): |
1558 |
continue
|
1559 |
if not isinstance(subscript.slice.value, astroid.Name): |
1560 |
continue
|
1561 |
if subscript.slice.value.name != node.target.name:
|
1562 |
continue
|
1563 |
if iterating_object.name != subscript.value.name:
|
1564 |
continue
|
1565 |
if subscript.value.scope() != node.scope():
|
1566 |
# Ignore this subscript if it's not in the same
|
1567 |
# scope. This means that in the body of the for
|
1568 |
# loop, another scope was created, where the same
|
1569 |
# name for the iterating object was used.
|
1570 |
continue
|
1571 |
self.add_message('consider-using-enumerate', node=node) |
1572 |
return
|
1573 |
|
1574 |
|
1575 |
def _is_one_arg_pos_call(call): |
1576 |
"""Is this a call with exactly 1 argument,
|
1577 |
where that argument is positional?
|
1578 |
"""
|
1579 |
return (isinstance(call, astroid.Call) |
1580 |
and len(call.args) == 1 and not call.keywords) |
1581 |
|
1582 |
|
1583 |
class ComparisonChecker(_BasicChecker): |
1584 |
"""Checks for comparisons
|
1585 |
|
1586 |
- singleton comparison: 'expr == True', 'expr == False' and 'expr == None'
|
1587 |
- yoda condition: 'const "comp" right' where comp can be '==', '!=', '<',
|
1588 |
'<=', '>' or '>=', and right can be a variable, an attribute, a method or
|
1589 |
a function
|
1590 |
"""
|
1591 |
msgs = {'C0121': ('Comparison to %s should be %s', |
1592 |
'singleton-comparison',
|
1593 |
'Used when an expression is compared to singleton '
|
1594 |
'values like True, False or None.'),
|
1595 |
'C0122': ('Comparison should be %s', |
1596 |
'misplaced-comparison-constant',
|
1597 |
'Used when the constant is placed on the left side'
|
1598 |
'of a comparison. It is usually clearer in intent to '
|
1599 |
'place it in the right hand side of the comparison.'),
|
1600 |
'C0123': ('Using type() instead of isinstance() for a typecheck.', |
1601 |
'unidiomatic-typecheck',
|
1602 |
'The idiomatic way to perform an explicit typecheck in '
|
1603 |
'Python is to use isinstance(x, Y) rather than '
|
1604 |
'type(x) == Y, type(x) is Y. Though there are unusual '
|
1605 |
'situations where these give different results.',
|
1606 |
{'old_names': [('W0154', 'unidiomatic-typecheck')]}), |
1607 |
} |
1608 |
|
1609 |
def _check_singleton_comparison(self, singleton, root_node): |
1610 |
if singleton.value is True: |
1611 |
suggestion = "just 'expr' or 'expr is True'"
|
1612 |
self.add_message('singleton-comparison', |
1613 |
node=root_node, |
1614 |
args=(True, suggestion))
|
1615 |
elif singleton.value is False: |
1616 |
suggestion = "'not expr' or 'expr is False'"
|
1617 |
self.add_message('singleton-comparison', |
1618 |
node=root_node, |
1619 |
args=(False, suggestion))
|
1620 |
elif singleton.value is None: |
1621 |
self.add_message('singleton-comparison', |
1622 |
node=root_node, |
1623 |
args=(None, "'expr is None'")) |
1624 |
|
1625 |
def _check_misplaced_constant(self, node, left, right, operator): |
1626 |
if isinstance(right, astroid.Const): |
1627 |
return
|
1628 |
operator = REVERSED_COMPS.get(operator, operator) |
1629 |
suggestion = '%s %s %r' % (right.as_string(), operator, left.value)
|
1630 |
self.add_message('misplaced-comparison-constant', node=node, |
1631 |
args=(suggestion,)) |
1632 |
|
1633 |
@check_messages('singleton-comparison', 'misplaced-comparison-constant', |
1634 |
'unidiomatic-typecheck')
|
1635 |
def visit_compare(self, node): |
1636 |
self._check_unidiomatic_typecheck(node)
|
1637 |
# NOTE: this checker only works with binary comparisons like 'x == 42'
|
1638 |
# but not 'x == y == 42'
|
1639 |
if len(node.ops) != 1: |
1640 |
return
|
1641 |
left = node.left |
1642 |
operator, right = node.ops[0]
|
1643 |
if (operator in ('<', '<=', '>', '>=', '!=', '==') |
1644 |
and isinstance(left, astroid.Const)): |
1645 |
self._check_misplaced_constant(node, left, right, operator)
|
1646 |
|
1647 |
if operator == '==': |
1648 |
if isinstance(left, astroid.Const): |
1649 |
self._check_singleton_comparison(left, node)
|
1650 |
elif isinstance(right, astroid.Const): |
1651 |
self._check_singleton_comparison(right, node)
|
1652 |
|
1653 |
def _check_unidiomatic_typecheck(self, node): |
1654 |
operator, right = node.ops[0]
|
1655 |
if operator in TYPECHECK_COMPARISON_OPERATORS: |
1656 |
left = node.left |
1657 |
if _is_one_arg_pos_call(left):
|
1658 |
self._check_type_x_is_y(node, left, operator, right)
|
1659 |
|
1660 |
def _check_type_x_is_y(self, node, left, operator, right): |
1661 |
"""Check for expressions like type(x) == Y."""
|
1662 |
left_func = safe_infer(left.func) |
1663 |
if not (isinstance(left_func, astroid.ClassDef) |
1664 |
and left_func.qname() == TYPE_QNAME):
|
1665 |
return
|
1666 |
|
1667 |
if operator in ('is', 'is not') and _is_one_arg_pos_call(right): |
1668 |
right_func = safe_infer(right.func) |
1669 |
if (isinstance(right_func, astroid.ClassDef) |
1670 |
and right_func.qname() == TYPE_QNAME):
|
1671 |
# type(x) == type(a)
|
1672 |
right_arg = safe_infer(right.args[0])
|
1673 |
if not isinstance(right_arg, LITERAL_NODE_TYPES): |
1674 |
# not e.g. type(x) == type([])
|
1675 |
return
|
1676 |
self.add_message('unidiomatic-typecheck', node=node) |
1677 |
|
1678 |
|
1679 |
class ElifChecker(BaseTokenChecker): |
1680 |
"""Checks needing to distinguish "else if" from "elif"
|
1681 |
|
1682 |
This checker mixes the astroid and the token approaches in order to create
|
1683 |
knowledge about whether a "else if" node is a true "else if" node, or a
|
1684 |
"elif" node.
|
1685 |
|
1686 |
The following checks depend on this implementation:
|
1687 |
- check for too many nested blocks (if/elif structures aren't considered
|
1688 |
as nested)
|
1689 |
- to be continued
|
1690 |
"""
|
1691 |
__implements__ = (ITokenChecker, IAstroidChecker) |
1692 |
name = 'elif'
|
1693 |
msgs = {'R0101': ('Too many nested blocks (%s/%s)', |
1694 |
'too-many-nested-blocks',
|
1695 |
'Used when a function or a method has too many nested '
|
1696 |
'blocks. This makes the code less understandable and '
|
1697 |
'maintainable.'),
|
1698 |
'R0102': ('The if statement can be reduced by %s', |
1699 |
'simplifiable-if-statement',
|
1700 |
'Used when an if statement can be reduced to a boolean '
|
1701 |
'conversion of the statement\'s test. '),
|
1702 |
} |
1703 |
options = (('max-nested-blocks',
|
1704 |
{'default' : 5, 'type' : 'int', 'metavar' : '<int>', |
1705 |
'help': 'Maximum number of nested blocks for function / ' |
1706 |
'method body'}
|
1707 |
),) |
1708 |
|
1709 |
def __init__(self, linter=None): |
1710 |
BaseTokenChecker.__init__(self, linter)
|
1711 |
self._init()
|
1712 |
|
1713 |
def _init(self): |
1714 |
self._nested_blocks = []
|
1715 |
self._elifs = []
|
1716 |
self._if_counter = 0 |
1717 |
self._nested_blocks_msg = None |
1718 |
|
1719 |
@staticmethod
|
1720 |
def _is_bool_const(node): |
1721 |
return (isinstance(node.value, astroid.Const) |
1722 |
and isinstance(node.value.value, bool)) |
1723 |
|
1724 |
def _is_actual_elif(self, node): |
1725 |
"""Check if the given node is an actual elif
|
1726 |
|
1727 |
This is a problem we're having with the builtin ast module,
|
1728 |
which splits `elif` branches into a separate if statement.
|
1729 |
Unfortunately we need to know the exact type in certain
|
1730 |
cases.
|
1731 |
"""
|
1732 |
|
1733 |
if isinstance(node.parent, astroid.If): |
1734 |
orelse = node.parent.orelse |
1735 |
# current if node must directly follow a "else"
|
1736 |
if orelse and orelse == [node]: |
1737 |
if self._elifs[self._if_counter]: |
1738 |
return True |
1739 |
return False |
1740 |
|
1741 |
def _check_simplifiable_if(self, node): |
1742 |
"""Check if the given if node can be simplified.
|
1743 |
|
1744 |
The if statement can be reduced to a boolean expression
|
1745 |
in some cases. For instance, if there are two branches
|
1746 |
and both of them return a boolean value that depends on
|
1747 |
the result of the statement's test, then this can be reduced
|
1748 |
to `bool(test)` without losing any functionality.
|
1749 |
"""
|
1750 |
|
1751 |
if self._is_actual_elif(node): |
1752 |
# Not interested in if statements with multiple branches.
|
1753 |
return
|
1754 |
if len(node.orelse) != 1 or len(node.body) != 1: |
1755 |
return
|
1756 |
|
1757 |
# Check if both branches can be reduced.
|
1758 |
first_branch = node.body[0]
|
1759 |
else_branch = node.orelse[0]
|
1760 |
if isinstance(first_branch, astroid.Return): |
1761 |
if not isinstance(else_branch, astroid.Return): |
1762 |
return
|
1763 |
first_branch_is_bool = self._is_bool_const(first_branch)
|
1764 |
else_branch_is_bool = self._is_bool_const(else_branch)
|
1765 |
reduced_to = "returning bool of test"
|
1766 |
elif isinstance(first_branch, astroid.Assign): |
1767 |
if not isinstance(else_branch, astroid.Assign): |
1768 |
return
|
1769 |
first_branch_is_bool = self._is_bool_const(first_branch)
|
1770 |
else_branch_is_bool = self._is_bool_const(else_branch)
|
1771 |
reduced_to = "assigning bool of test"
|
1772 |
else:
|
1773 |
return
|
1774 |
|
1775 |
if not first_branch_is_bool or not else_branch_is_bool: |
1776 |
return
|
1777 |
if not first_branch.value.value: |
1778 |
# This is a case that can't be easily simplified and
|
1779 |
# if it can be simplified, it will usually result in a
|
1780 |
# code that's harder to understand and comprehend.
|
1781 |
# Let's take for instance `arg and arg <= 3`. This could theoretically be
|
1782 |
# reduced to `not arg or arg > 3`, but the net result is that now the
|
1783 |
# condition is harder to understand, because it requires understanding of
|
1784 |
# an extra clause:
|
1785 |
# * first, there is the negation of truthness with `not arg`
|
1786 |
# * the second clause is `arg > 3`, which occurs when arg has a
|
1787 |
# a truth value, but it implies that `arg > 3` is equivalent
|
1788 |
# with `arg and arg > 3`, which means that the user must
|
1789 |
# think about this assumption when evaluating `arg > 3`.
|
1790 |
# The original form is easier to grasp.
|
1791 |
return
|
1792 |
|
1793 |
self.add_message('simplifiable-if-statement', node=node, |
1794 |
args=(reduced_to, )) |
1795 |
|
1796 |
def process_tokens(self, tokens): |
1797 |
# Process tokens and look for 'if' or 'elif'
|
1798 |
for _, token, _, _, _ in tokens: |
1799 |
if token == 'elif': |
1800 |
self._elifs.append(True) |
1801 |
elif token == 'if': |
1802 |
self._elifs.append(False) |
1803 |
|
1804 |
def leave_module(self, _): |
1805 |
self._init()
|
1806 |
|
1807 |
@check_messages('too-many-nested-blocks') |
1808 |
def visit_tryexcept(self, node): |
1809 |
self._check_nested_blocks(node)
|
1810 |
|
1811 |
visit_tryfinally = visit_tryexcept |
1812 |
visit_while = visit_tryexcept |
1813 |
visit_for = visit_while |
1814 |
|
1815 |
def visit_ifexp(self, _): |
1816 |
self._if_counter += 1 |
1817 |
|
1818 |
def visit_comprehension(self, node): |
1819 |
self._if_counter += len(node.ifs) |
1820 |
|
1821 |
@check_messages('too-many-nested-blocks', 'simplifiable-if-statement') |
1822 |
def visit_if(self, node): |
1823 |
self._check_simplifiable_if(node)
|
1824 |
self._check_nested_blocks(node)
|
1825 |
self._if_counter += 1 |
1826 |
|
1827 |
@check_messages('too-many-nested-blocks') |
1828 |
def leave_functiondef(self, _): |
1829 |
# new scope = reinitialize the stack of nested blocks
|
1830 |
self._nested_blocks = []
|
1831 |
# if there is a waiting message left, send it
|
1832 |
if self._nested_blocks_msg: |
1833 |
self.add_message('too-many-nested-blocks', |
1834 |
node=self._nested_blocks_msg[0], |
1835 |
args=self._nested_blocks_msg[1]) |
1836 |
self._nested_blocks_msg = None |
1837 |
|
1838 |
def _check_nested_blocks(self, node): |
1839 |
"""Update and check the number of nested blocks
|
1840 |
"""
|
1841 |
# only check block levels inside functions or methods
|
1842 |
if not isinstance(node.scope(), astroid.FunctionDef): |
1843 |
return
|
1844 |
# messages are triggered on leaving the nested block. Here we save the
|
1845 |
# stack in case the current node isn't nested in the previous one
|
1846 |
nested_blocks = self._nested_blocks[:]
|
1847 |
if node.parent == node.scope():
|
1848 |
self._nested_blocks = [node]
|
1849 |
else:
|
1850 |
# go through ancestors from the most nested to the less
|
1851 |
for ancestor_node in reversed(self._nested_blocks): |
1852 |
if ancestor_node == node.parent:
|
1853 |
break
|
1854 |
self._nested_blocks.pop()
|
1855 |
# if the node is a elif, this should not be another nesting level
|
1856 |
if isinstance(node, astroid.If) and self._elifs[self._if_counter]: |
1857 |
if self._nested_blocks: |
1858 |
self._nested_blocks.pop()
|
1859 |
self._nested_blocks.append(node)
|
1860 |
# send message only once per group of nested blocks
|
1861 |
if len(nested_blocks) > self.config.max_nested_blocks: |
1862 |
if len(nested_blocks) > len(self._nested_blocks): |
1863 |
self.add_message('too-many-nested-blocks', node=nested_blocks[0], |
1864 |
args=(len(nested_blocks),
|
1865 |
self.config.max_nested_blocks))
|
1866 |
self._nested_blocks_msg = None |
1867 |
else:
|
1868 |
# if time has not come yet to send the message (ie the stack of
|
1869 |
# nested nodes is still increasing), save it in case the
|
1870 |
# current node is the last one of the function
|
1871 |
self._nested_blocks_msg = (self._nested_blocks[0], |
1872 |
(len(self._nested_blocks), |
1873 |
self.config.max_nested_blocks))
|
1874 |
|
1875 |
class NotChecker(_BasicChecker): |
1876 |
"""checks for too many not in comparison expressions
|
1877 |
|
1878 |
- "not not" should trigger a warning
|
1879 |
- "not" followed by a comparison should trigger a warning
|
1880 |
"""
|
1881 |
msgs = {'C0113': ('Consider changing "%s" to "%s"', |
1882 |
'unneeded-not',
|
1883 |
'Used when a boolean expression contains an unneeded '
|
1884 |
'negation.'),
|
1885 |
} |
1886 |
|
1887 |
reverse_op = {'<': '>=', '<=': '>', '>': '<=', '>=': '<', '==': '!=', |
1888 |
'!=': '==', 'in': 'not in', 'is': 'is not'} |
1889 |
# sets are not ordered, so for example "not set(LEFT_VALS) <= set(RIGHT_VALS)" is
|
1890 |
# not equivalent to "set(LEFT_VALS) > set(RIGHT_VALS)"
|
1891 |
skipped_nodes = (astroid.Set, ) |
1892 |
# 'builtins' py3, '__builtin__' py2
|
1893 |
skipped_classnames = ['%s.%s' % (six.moves.builtins.__name__, qname)
|
1894 |
for qname in ('set', 'frozenset')] |
1895 |
|
1896 |
@check_messages('unneeded-not') |
1897 |
def visit_unaryop(self, node): |
1898 |
if node.op != 'not': |
1899 |
return
|
1900 |
operand = node.operand |
1901 |
|
1902 |
if isinstance(operand, astroid.UnaryOp) and operand.op == 'not': |
1903 |
self.add_message('unneeded-not', node=node, |
1904 |
args=(node.as_string(), |
1905 |
operand.operand.as_string())) |
1906 |
elif isinstance(operand, astroid.Compare): |
1907 |
left = operand.left |
1908 |
# ignore multiple comparisons
|
1909 |
if len(operand.ops) > 1: |
1910 |
return
|
1911 |
operator, right = operand.ops[0]
|
1912 |
if operator not in self.reverse_op: |
1913 |
return
|
1914 |
# Ignore __ne__ as function of __eq__
|
1915 |
frame = node.frame() |
1916 |
if frame.name == '__ne__' and operator == '==': |
1917 |
return
|
1918 |
for _type in (_node_type(left), _node_type(right)): |
1919 |
if not _type: |
1920 |
return
|
1921 |
if isinstance(_type, self.skipped_nodes): |
1922 |
return
|
1923 |
if (isinstance(_type, astroid.Instance) and |
1924 |
_type.qname() in self.skipped_classnames): |
1925 |
return
|
1926 |
suggestion = '%s %s %s' % (left.as_string(),
|
1927 |
self.reverse_op[operator],
|
1928 |
right.as_string()) |
1929 |
self.add_message('unneeded-not', node=node, |
1930 |
args=(node.as_string(), suggestion)) |
1931 |
|
1932 |
|
1933 |
class MultipleTypesChecker(BaseChecker): |
1934 |
"""Checks for variable type redefinitions (NoneType excepted)
|
1935 |
|
1936 |
At a function, method, class or module scope
|
1937 |
|
1938 |
This rule could be improved:
|
1939 |
- Currently, if an attribute is set to different types in 2 methods of a
|
1940 |
same class, it won't be detected (see functional test)
|
1941 |
- One could improve the support for inference on assignment with tuples,
|
1942 |
ifexpr, etc. Also it would be great to have support for inference on
|
1943 |
str.split()
|
1944 |
"""
|
1945 |
__implements__ = IAstroidChecker |
1946 |
|
1947 |
name = 'multiple_types'
|
1948 |
msgs = {'R0204': ('Redefinition of %s type from %s to %s', |
1949 |
'redefined-variable-type',
|
1950 |
'Used when the type of a variable changes inside a '
|
1951 |
'method or a function.'
|
1952 |
), |
1953 |
} |
1954 |
|
1955 |
def visit_classdef(self, _): |
1956 |
self._assigns.append({})
|
1957 |
|
1958 |
@check_messages('redefined-variable-type') |
1959 |
def leave_classdef(self, _): |
1960 |
self._check_and_add_messages()
|
1961 |
|
1962 |
visit_functiondef = visit_classdef |
1963 |
leave_functiondef = leave_module = leave_classdef |
1964 |
|
1965 |
def visit_module(self, _): |
1966 |
self._assigns = [{}]
|
1967 |
|
1968 |
def _check_and_add_messages(self): |
1969 |
assigns = self._assigns.pop()
|
1970 |
for name, args in assigns.items(): |
1971 |
if len(args) <= 1: |
1972 |
continue
|
1973 |
_, orig_type = args[0]
|
1974 |
# Check if there is a type in the following nodes that would be
|
1975 |
# different from orig_type.
|
1976 |
for redef_node, redef_type in args[1:]: |
1977 |
if redef_type != orig_type:
|
1978 |
orig_type = orig_type.replace(BUILTINS + ".", '') |
1979 |
redef_type = redef_type.replace(BUILTINS + ".", '') |
1980 |
self.add_message('redefined-variable-type', node=redef_node, |
1981 |
args=(name, orig_type, redef_type)) |
1982 |
break
|
1983 |
|
1984 |
def visit_assign(self, node): |
1985 |
# we don't handle multiple assignment nor slice assignment
|
1986 |
target = node.targets[0]
|
1987 |
if isinstance(target, (astroid.Tuple, astroid.Subscript)): |
1988 |
return
|
1989 |
# ignore NoneType
|
1990 |
if _is_none(node):
|
1991 |
return
|
1992 |
_type = _node_type(node.value) |
1993 |
if _type:
|
1994 |
self._assigns[-1].setdefault(target.as_string(), []).append( |
1995 |
(node, _type.pytype())) |
1996 |
|
1997 |
|
1998 |
def register(linter): |
1999 |
"""required method to auto register this checker"""
|
2000 |
linter.register_checker(BasicErrorChecker(linter)) |
2001 |
linter.register_checker(BasicChecker(linter)) |
2002 |
linter.register_checker(NameChecker(linter)) |
2003 |
linter.register_checker(DocStringChecker(linter)) |
2004 |
linter.register_checker(PassChecker(linter)) |
2005 |
linter.register_checker(LambdaForComprehensionChecker(linter)) |
2006 |
linter.register_checker(ComparisonChecker(linter)) |
2007 |
linter.register_checker(NotChecker(linter)) |
2008 |
linter.register_checker(RecommandationChecker(linter)) |
2009 |
linter.register_checker(ElifChecker(linter)) |
2010 |
linter.register_checker(MultipleTypesChecker(linter)) |