OLD | NEW |
1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). |
2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr | 2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # Copyright (c) 2009-2010 Arista Networks, Inc. | 3 # Copyright (c) 2009-2010 Arista Networks, Inc. |
4 # | 4 # |
5 # This program is free software; you can redistribute it and/or modify it under | 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 | 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 | 7 # Foundation; either version 2 of the License, or (at your option) any later |
8 # version. | 8 # version. |
9 # | 9 # |
10 # This program is distributed in the hope that it will be useful, but WITHOUT | 10 # This program is distributed in the hope that it will be useful, but WITHOUT |
(...skipping 27 matching lines...) Expand all Loading... |
38 check_messages, | 38 check_messages, |
39 clobber_in_except, | 39 clobber_in_except, |
40 is_builtin_object, | 40 is_builtin_object, |
41 is_inside_except, | 41 is_inside_except, |
42 overrides_a_method, | 42 overrides_a_method, |
43 safe_infer, | 43 safe_infer, |
44 get_argument_from_call, | 44 get_argument_from_call, |
45 has_known_bases, | 45 has_known_bases, |
46 NoSuchArgumentError, | 46 NoSuchArgumentError, |
47 is_import_error, | 47 is_import_error, |
| 48 unimplemented_abstract_methods, |
48 ) | 49 ) |
49 | 50 |
50 | 51 |
51 # regex for class/function/variable/constant name | 52 # regex for class/function/variable/constant name |
52 CLASS_NAME_RGX = re.compile('[A-Z_][a-zA-Z0-9]+$') | 53 CLASS_NAME_RGX = re.compile('[A-Z_][a-zA-Z0-9]+$') |
53 MOD_NAME_RGX = re.compile('(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$') | 54 MOD_NAME_RGX = re.compile('(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$') |
54 CONST_NAME_RGX = re.compile('(([A-Z_][A-Z0-9_]*)|(__.*__))$') | 55 CONST_NAME_RGX = re.compile('(([A-Z_][A-Z0-9_]*)|(__.*__))$') |
55 COMP_VAR_RGX = re.compile('[A-Za-z_][A-Za-z0-9_]*$') | 56 COMP_VAR_RGX = re.compile('[A-Za-z_][A-Za-z0-9_]*$') |
56 DEFAULT_NAME_RGX = re.compile('[a-z_][a-z0-9_]{2,30}$') | 57 DEFAULT_NAME_RGX = re.compile('[a-z_][a-z0-9_]{2,30}$') |
57 CLASS_ATTRIBUTE_RGX = re.compile(r'([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$') | 58 CLASS_ATTRIBUTE_RGX = re.compile(r'([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$') |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
141 return (match is not None and | 142 return (match is not None and |
142 match.lastgroup is not None and | 143 match.lastgroup is not None and |
143 match.lastgroup not in EXEMPT_NAME_CATEGORIES | 144 match.lastgroup not in EXEMPT_NAME_CATEGORIES |
144 and (node_type != 'method' or confidence != INFERENCE_FAILURE)) | 145 and (node_type != 'method' or confidence != INFERENCE_FAILURE)) |
145 | 146 |
146 | 147 |
147 if sys.version_info < (3, 0): | 148 if sys.version_info < (3, 0): |
148 PROPERTY_CLASSES = set(('__builtin__.property', 'abc.abstractproperty')) | 149 PROPERTY_CLASSES = set(('__builtin__.property', 'abc.abstractproperty')) |
149 else: | 150 else: |
150 PROPERTY_CLASSES = set(('builtins.property', 'abc.abstractproperty')) | 151 PROPERTY_CLASSES = set(('builtins.property', 'abc.abstractproperty')) |
151 ABC_METHODS = set(('abc.abstractproperty', 'abc.abstractmethod', | 152 |
152 'abc.abstractclassmethod', 'abc.abstractstaticmethod')) | |
153 | 153 |
154 def _determine_function_name_type(node): | 154 def _determine_function_name_type(node): |
155 """Determine the name type whose regex the a function's name should match. | 155 """Determine the name type whose regex the a function's name should match. |
156 | 156 |
157 :param node: A function node. | 157 :param node: A function node. |
158 :returns: One of ('function', 'method', 'attr') | 158 :returns: One of ('function', 'method', 'attr') |
159 """ | 159 """ |
160 if not node.is_method(): | 160 if not node.is_method(): |
161 return 'function' | 161 return 'function' |
162 if node.decorators: | 162 if node.decorators: |
163 decorators = node.decorators.nodes | 163 decorators = node.decorators.nodes |
164 else: | 164 else: |
165 decorators = [] | 165 decorators = [] |
166 for decorator in decorators: | 166 for decorator in decorators: |
167 # If the function is a property (decorated with @property | 167 # If the function is a property (decorated with @property |
168 # or @abc.abstractproperty), the name type is 'attr'. | 168 # or @abc.abstractproperty), the name type is 'attr'. |
169 if (isinstance(decorator, astroid.Name) or | 169 if (isinstance(decorator, astroid.Name) or |
170 (isinstance(decorator, astroid.Getattr) and | 170 (isinstance(decorator, astroid.Getattr) and |
171 decorator.attrname == 'abstractproperty')): | 171 decorator.attrname == 'abstractproperty')): |
172 infered = safe_infer(decorator) | 172 infered = safe_infer(decorator) |
173 if infered and infered.qname() in PROPERTY_CLASSES: | 173 if infered and infered.qname() in PROPERTY_CLASSES: |
174 return 'attr' | 174 return 'attr' |
175 # If the function is decorated using the prop_method.{setter,getter} | 175 # If the function is decorated using the prop_method.{setter,getter} |
176 # form, treat it like an attribute as well. | 176 # form, treat it like an attribute as well. |
177 elif (isinstance(decorator, astroid.Getattr) and | 177 elif (isinstance(decorator, astroid.Getattr) and |
178 decorator.attrname in ('setter', 'deleter')): | 178 decorator.attrname in ('setter', 'deleter')): |
179 return 'attr' | 179 return 'attr' |
180 return 'method' | 180 return 'method' |
181 | 181 |
182 def decorated_with_abc(func): | 182 |
183 """ Determine if the `func` node is decorated | 183 |
184 with `abc` decorators (abstractmethod et co.) | 184 def _has_abstract_methods(node): |
185 """ | 185 """ |
186 if func.decorators: | 186 Determine if the given `node` has abstract methods. |
187 for node in func.decorators.nodes: | |
188 try: | |
189 infered = next(node.infer()) | |
190 except InferenceError: | |
191 continue | |
192 if infered and infered.qname() in ABC_METHODS: | |
193 return True | |
194 | 187 |
195 def has_abstract_methods(node): | 188 The methods should be made abstract by decorating them |
| 189 with `abc` decorators. |
196 """ | 190 """ |
197 Determine if the given `node` has | 191 return len(unimplemented_abstract_methods(node)) > 0 |
198 abstract methods, defined with `abc` module. | 192 |
199 """ | |
200 return any(decorated_with_abc(meth) | |
201 for meth in node.methods()) | |
202 | 193 |
203 def report_by_type_stats(sect, stats, old_stats): | 194 def report_by_type_stats(sect, stats, old_stats): |
204 """make a report of | 195 """make a report of |
205 | 196 |
206 * percentage of different types documented | 197 * percentage of different types documented |
207 * percentage of different types with a bad name | 198 * percentage of different types with a bad name |
208 """ | 199 """ |
209 # percentage of different types documented and/or with a bad name | 200 # percentage of different types documented and/or with a bad name |
210 nice_stats = {} | 201 nice_stats = {} |
211 for node_type in ('module', 'class', 'method', 'function'): | 202 for node_type in ('module', 'class', 'method', 'function'): |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
291 '"yield" statements).', | 282 '"yield" statements).', |
292 {'maxversion': (3, 3)}), | 283 {'maxversion': (3, 3)}), |
293 'E0107': ("Use of the non-existent %s operator", | 284 'E0107': ("Use of the non-existent %s operator", |
294 'nonexistent-operator', | 285 'nonexistent-operator', |
295 "Used when you attempt to use the C-style pre-increment or" | 286 "Used when you attempt to use the C-style pre-increment or" |
296 "pre-decrement operator -- and ++, which doesn't exist in Pyth
on."), | 287 "pre-decrement operator -- and ++, which doesn't exist in Pyth
on."), |
297 'E0108': ('Duplicate argument name %s in function definition', | 288 'E0108': ('Duplicate argument name %s in function definition', |
298 'duplicate-argument-name', | 289 'duplicate-argument-name', |
299 'Duplicate argument names in function definitions are syntax' | 290 'Duplicate argument names in function definitions are syntax' |
300 ' errors.'), | 291 ' errors.'), |
301 'E0110': ('Abstract class with abstract methods instantiated', | 292 'E0110': ('Abstract class %r with abstract methods instantiated', |
302 'abstract-class-instantiated', | 293 'abstract-class-instantiated', |
303 'Used when an abstract class with `abc.ABCMeta` as metaclass ' | 294 'Used when an abstract class with `abc.ABCMeta` as metaclass ' |
304 'has abstract methods and is instantiated.'), | 295 'has abstract methods and is instantiated.'), |
305 'W0120': ('Else clause on loop without a break statement', | 296 'W0120': ('Else clause on loop without a break statement', |
306 'useless-else-on-loop', | 297 'useless-else-on-loop', |
307 'Loops should only have an else clause if they can exit early
' | 298 'Loops should only have an else clause if they can exit early
' |
308 'with a break statement, otherwise the statements under else ' | 299 'with a break statement, otherwise the statements under else ' |
309 'should be on the same scope as the loop itself.'), | 300 'should be on the same scope as the loop itself.'), |
310 } | 301 } |
311 | 302 |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
391 abc.ABCMeta as metaclass. | 382 abc.ABCMeta as metaclass. |
392 """ | 383 """ |
393 try: | 384 try: |
394 infered = next(node.func.infer()) | 385 infered = next(node.func.infer()) |
395 except astroid.InferenceError: | 386 except astroid.InferenceError: |
396 return | 387 return |
397 if not isinstance(infered, astroid.Class): | 388 if not isinstance(infered, astroid.Class): |
398 return | 389 return |
399 # __init__ was called | 390 # __init__ was called |
400 metaclass = infered.metaclass() | 391 metaclass = infered.metaclass() |
401 abstract_methods = has_abstract_methods(infered) | 392 abstract_methods = _has_abstract_methods(infered) |
402 if metaclass is None: | 393 if metaclass is None: |
403 # Python 3.4 has `abc.ABC`, which won't be detected | 394 # Python 3.4 has `abc.ABC`, which won't be detected |
404 # by ClassNode.metaclass() | 395 # by ClassNode.metaclass() |
405 for ancestor in infered.ancestors(): | 396 for ancestor in infered.ancestors(): |
406 if ancestor.qname() == 'abc.ABC' and abstract_methods: | 397 if ancestor.qname() == 'abc.ABC' and abstract_methods: |
407 self.add_message('abstract-class-instantiated', node=node) | 398 self.add_message('abstract-class-instantiated', |
| 399 args=(infered.name, ), |
| 400 node=node) |
408 break | 401 break |
409 return | 402 return |
410 if metaclass.qname() == 'abc.ABCMeta' and abstract_methods: | 403 if metaclass.qname() == 'abc.ABCMeta' and abstract_methods: |
411 self.add_message('abstract-class-instantiated', node=node) | 404 self.add_message('abstract-class-instantiated', |
| 405 args=(infered.name, ), |
| 406 node=node) |
412 | 407 |
413 def _check_else_on_loop(self, node): | 408 def _check_else_on_loop(self, node): |
414 """Check that any loop with an else clause has a break statement.""" | 409 """Check that any loop with an else clause has a break statement.""" |
415 if node.orelse and not _loop_exits_early(node): | 410 if node.orelse and not _loop_exits_early(node): |
416 self.add_message('useless-else-on-loop', node=node, | 411 self.add_message('useless-else-on-loop', node=node, |
417 # This is not optimal, but the line previous | 412 # This is not optimal, but the line previous |
418 # to the first statement in the else clause | 413 # to the first statement in the else clause |
419 # will usually be the one that contains the else:. | 414 # will usually be the one that contains the else:. |
420 line=node.orelse[0].lineno - 1) | 415 line=node.orelse[0].lineno - 1) |
421 | 416 |
(...skipping 247 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
669 # return something else (but we don't check that, yet). | 664 # return something else (but we don't check that, yet). |
670 return | 665 return |
671 self.add_message('unnecessary-lambda', line=node.fromlineno, node=node) | 666 self.add_message('unnecessary-lambda', line=node.fromlineno, node=node) |
672 | 667 |
673 @check_messages('dangerous-default-value') | 668 @check_messages('dangerous-default-value') |
674 def visit_function(self, node): | 669 def visit_function(self, node): |
675 """check function name, docstring, arguments, redefinition, | 670 """check function name, docstring, arguments, redefinition, |
676 variable names, max locals | 671 variable names, max locals |
677 """ | 672 """ |
678 self.stats[node.is_method() and 'method' or 'function'] += 1 | 673 self.stats[node.is_method() and 'method' or 'function'] += 1 |
| 674 self._check_dangerous_default(node) |
| 675 |
| 676 def _check_dangerous_default(self, node): |
679 # check for dangerous default values as arguments | 677 # check for dangerous default values as arguments |
| 678 is_iterable = lambda n: isinstance(n, (astroid.List, |
| 679 astroid.Set, |
| 680 astroid.Dict)) |
680 for default in node.args.defaults: | 681 for default in node.args.defaults: |
681 try: | 682 try: |
682 value = next(default.infer()) | 683 value = next(default.infer()) |
683 except astroid.InferenceError: | 684 except astroid.InferenceError: |
684 continue | 685 continue |
685 | 686 |
686 if (isinstance(value, astroid.Instance) and | 687 if (isinstance(value, astroid.Instance) and |
687 value.qname() in DEFAULT_ARGUMENT_SYMBOLS): | 688 value.qname() in DEFAULT_ARGUMENT_SYMBOLS): |
| 689 |
688 if value is default: | 690 if value is default: |
689 msg = DEFAULT_ARGUMENT_SYMBOLS[value.qname()] | 691 msg = DEFAULT_ARGUMENT_SYMBOLS[value.qname()] |
690 elif type(value) is astroid.Instance: | 692 elif type(value) is astroid.Instance or is_iterable(value): |
691 if isinstance(default, astroid.CallFunc): | 693 # We are here in the following situation(s): |
692 # this argument is direct call to list() or dict() etc | 694 # * a dict/set/list/tuple call which wasn't inferred |
| 695 # to a syntax node ({}, () etc.). This can happen |
| 696 # when the arguments are invalid or unknown to |
| 697 # the inference. |
| 698 # * a variable from somewhere else, which turns out to be
a list |
| 699 # or a dict. |
| 700 if is_iterable(default): |
| 701 msg = value.pytype() |
| 702 elif isinstance(default, astroid.CallFunc): |
693 msg = '%s() (%s)' % (value.name, value.qname()) | 703 msg = '%s() (%s)' % (value.name, value.qname()) |
694 else: | 704 else: |
695 # this argument is a variable from somewhere else which
turns | |
696 # out to be a list or dict | |
697 msg = '%s (%s)' % (default.as_string(), value.qname()) | 705 msg = '%s (%s)' % (default.as_string(), value.qname()) |
698 else: | 706 else: |
699 # this argument is a name | 707 # this argument is a name |
700 msg = '%s (%s)' % (default.as_string(), | 708 msg = '%s (%s)' % (default.as_string(), |
701 DEFAULT_ARGUMENT_SYMBOLS[value.qname()]) | 709 DEFAULT_ARGUMENT_SYMBOLS[value.qname()]) |
702 self.add_message('dangerous-default-value', node=node, args=(msg
,)) | 710 self.add_message('dangerous-default-value', |
| 711 node=node, |
| 712 args=(msg, )) |
703 | 713 |
704 @check_messages('unreachable', 'lost-exception') | 714 @check_messages('unreachable', 'lost-exception') |
705 def visit_return(self, node): | 715 def visit_return(self, node): |
706 """1 - check is the node has a right sibling (if so, that's some | 716 """1 - check is the node has a right sibling (if so, that's some |
707 unreachable code) | 717 unreachable code) |
708 2 - check is the node is inside the finally clause of a try...finally | 718 2 - check is the node is inside the finally clause of a try...finally |
709 block | 719 block |
710 """ | 720 """ |
711 self._check_unreachable(node) | 721 self._check_unreachable(node) |
712 # Is it inside final body of a try...finally bloc ? | 722 # Is it inside final body of a try...finally bloc ? |
(...skipping 516 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1229 | 1239 |
1230 | 1240 |
1231 def register(linter): | 1241 def register(linter): |
1232 """required method to auto register this checker""" | 1242 """required method to auto register this checker""" |
1233 linter.register_checker(BasicErrorChecker(linter)) | 1243 linter.register_checker(BasicErrorChecker(linter)) |
1234 linter.register_checker(BasicChecker(linter)) | 1244 linter.register_checker(BasicChecker(linter)) |
1235 linter.register_checker(NameChecker(linter)) | 1245 linter.register_checker(NameChecker(linter)) |
1236 linter.register_checker(DocStringChecker(linter)) | 1246 linter.register_checker(DocStringChecker(linter)) |
1237 linter.register_checker(PassChecker(linter)) | 1247 linter.register_checker(PassChecker(linter)) |
1238 linter.register_checker(LambdaForComprehensionChecker(linter)) | 1248 linter.register_checker(LambdaForComprehensionChecker(linter)) |
OLD | NEW |