OLD | NEW |
(Empty) | |
| 1 # pylint: disable=W0611 |
| 2 # |
| 3 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). |
| 4 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
| 5 # |
| 6 # This program is free software; you can redistribute it and/or modify it under |
| 7 # the terms of the GNU General Public License as published by the Free Software |
| 8 # Foundation; either version 2 of the License, or (at your option) any later |
| 9 # version. |
| 10 # |
| 11 # This program is distributed in the hope that it will be useful, but WITHOUT |
| 12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 13 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 14 # |
| 15 # You should have received a copy of the GNU General Public License along with |
| 16 # this program; if not, write to the Free Software Foundation, Inc., |
| 17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 18 """some functions that may be useful for various checkers |
| 19 """ |
| 20 |
| 21 import re |
| 22 import sys |
| 23 import string |
| 24 |
| 25 import astroid |
| 26 from astroid import scoped_nodes |
| 27 from logilab.common.compat import builtins |
| 28 |
| 29 BUILTINS_NAME = builtins.__name__ |
| 30 COMP_NODE_TYPES = astroid.ListComp, astroid.SetComp, astroid.DictComp, astroid.G
enExpr |
| 31 PY3K = sys.version_info[0] == 3 |
| 32 |
| 33 if not PY3K: |
| 34 EXCEPTIONS_MODULE = "exceptions" |
| 35 else: |
| 36 EXCEPTIONS_MODULE = "builtins" |
| 37 ABC_METHODS = set(('abc.abstractproperty', 'abc.abstractmethod', |
| 38 'abc.abstractclassmethod', 'abc.abstractstaticmethod')) |
| 39 |
| 40 |
| 41 class NoSuchArgumentError(Exception): |
| 42 pass |
| 43 |
| 44 def is_inside_except(node): |
| 45 """Returns true if node is inside the name of an except handler.""" |
| 46 current = node |
| 47 while current and not isinstance(current.parent, astroid.ExceptHandler): |
| 48 current = current.parent |
| 49 |
| 50 return current and current is current.parent.name |
| 51 |
| 52 |
| 53 def get_all_elements(node): |
| 54 """Recursively returns all atoms in nested lists and tuples.""" |
| 55 if isinstance(node, (astroid.Tuple, astroid.List)): |
| 56 for child in node.elts: |
| 57 for e in get_all_elements(child): |
| 58 yield e |
| 59 else: |
| 60 yield node |
| 61 |
| 62 |
| 63 def clobber_in_except(node): |
| 64 """Checks if an assignment node in an except handler clobbers an existing |
| 65 variable. |
| 66 |
| 67 Returns (True, args for W0623) if assignment clobbers an existing variable, |
| 68 (False, None) otherwise. |
| 69 """ |
| 70 if isinstance(node, astroid.AssAttr): |
| 71 return (True, (node.attrname, 'object %r' % (node.expr.as_string(),))) |
| 72 elif isinstance(node, astroid.AssName): |
| 73 name = node.name |
| 74 if is_builtin(name): |
| 75 return (True, (name, 'builtins')) |
| 76 else: |
| 77 stmts = node.lookup(name)[1] |
| 78 if (stmts and not isinstance(stmts[0].ass_type(), |
| 79 (astroid.Assign, astroid.AugAssign, |
| 80 astroid.ExceptHandler))): |
| 81 return (True, (name, 'outer scope (line %s)' % stmts[0].fromline
no)) |
| 82 return (False, None) |
| 83 |
| 84 |
| 85 def safe_infer(node): |
| 86 """return the inferred value for the given node. |
| 87 Return None if inference failed or if there is some ambiguity (more than |
| 88 one node has been inferred) |
| 89 """ |
| 90 try: |
| 91 inferit = node.infer() |
| 92 value = next(inferit) |
| 93 except astroid.InferenceError: |
| 94 return |
| 95 try: |
| 96 next(inferit) |
| 97 return # None if there is ambiguity on the inferred node |
| 98 except astroid.InferenceError: |
| 99 return # there is some kind of ambiguity |
| 100 except StopIteration: |
| 101 return value |
| 102 |
| 103 def is_super(node): |
| 104 """return True if the node is referencing the "super" builtin function |
| 105 """ |
| 106 if getattr(node, 'name', None) == 'super' and \ |
| 107 node.root().name == BUILTINS_NAME: |
| 108 return True |
| 109 return False |
| 110 |
| 111 def is_error(node): |
| 112 """return true if the function does nothing but raising an exception""" |
| 113 for child_node in node.get_children(): |
| 114 if isinstance(child_node, astroid.Raise): |
| 115 return True |
| 116 return False |
| 117 |
| 118 def is_raising(body): |
| 119 """return true if the given statement node raise an exception""" |
| 120 for node in body: |
| 121 if isinstance(node, astroid.Raise): |
| 122 return True |
| 123 return False |
| 124 |
| 125 def is_empty(body): |
| 126 """return true if the given node does nothing but 'pass'""" |
| 127 return len(body) == 1 and isinstance(body[0], astroid.Pass) |
| 128 |
| 129 builtins = builtins.__dict__.copy() |
| 130 SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__') |
| 131 |
| 132 def is_builtin_object(node): |
| 133 """Returns True if the given node is an object from the __builtin__ module."
"" |
| 134 return node and node.root().name == BUILTINS_NAME |
| 135 |
| 136 def is_builtin(name): # was is_native_builtin |
| 137 """return true if <name> could be considered as a builtin defined by python |
| 138 """ |
| 139 if name in builtins: |
| 140 return True |
| 141 if name in SPECIAL_BUILTINS: |
| 142 return True |
| 143 return False |
| 144 |
| 145 def is_defined_before(var_node): |
| 146 """return True if the variable node is defined by a parent node (list, |
| 147 set, dict, or generator comprehension, lambda) or in a previous sibling |
| 148 node on the same line (statement_defining ; statement_using) |
| 149 """ |
| 150 varname = var_node.name |
| 151 _node = var_node.parent |
| 152 while _node: |
| 153 if isinstance(_node, COMP_NODE_TYPES): |
| 154 for ass_node in _node.nodes_of_class(astroid.AssName): |
| 155 if ass_node.name == varname: |
| 156 return True |
| 157 elif isinstance(_node, astroid.For): |
| 158 for ass_node in _node.target.nodes_of_class(astroid.AssName): |
| 159 if ass_node.name == varname: |
| 160 return True |
| 161 elif isinstance(_node, astroid.With): |
| 162 for expr, ids in _node.items: |
| 163 if expr.parent_of(var_node): |
| 164 break |
| 165 if (ids and |
| 166 isinstance(ids, astroid.AssName) and |
| 167 ids.name == varname): |
| 168 return True |
| 169 elif isinstance(_node, (astroid.Lambda, astroid.Function)): |
| 170 if _node.args.is_argument(varname): |
| 171 return True |
| 172 if getattr(_node, 'name', None) == varname: |
| 173 return True |
| 174 break |
| 175 elif isinstance(_node, astroid.ExceptHandler): |
| 176 if isinstance(_node.name, astroid.AssName): |
| 177 ass_node = _node.name |
| 178 if ass_node.name == varname: |
| 179 return True |
| 180 _node = _node.parent |
| 181 # possibly multiple statements on the same line using semi colon separator |
| 182 stmt = var_node.statement() |
| 183 _node = stmt.previous_sibling() |
| 184 lineno = stmt.fromlineno |
| 185 while _node and _node.fromlineno == lineno: |
| 186 for ass_node in _node.nodes_of_class(astroid.AssName): |
| 187 if ass_node.name == varname: |
| 188 return True |
| 189 for imp_node in _node.nodes_of_class((astroid.From, astroid.Import)): |
| 190 if varname in [name[1] or name[0] for name in imp_node.names]: |
| 191 return True |
| 192 _node = _node.previous_sibling() |
| 193 return False |
| 194 |
| 195 def is_func_default(node): |
| 196 """return true if the given Name node is used in function default argument's |
| 197 value |
| 198 """ |
| 199 parent = node.scope() |
| 200 if isinstance(parent, astroid.Function): |
| 201 for default_node in parent.args.defaults: |
| 202 for default_name_node in default_node.nodes_of_class(astroid.Name): |
| 203 if default_name_node is node: |
| 204 return True |
| 205 return False |
| 206 |
| 207 def is_func_decorator(node): |
| 208 """return true if the name is used in function decorator""" |
| 209 parent = node.parent |
| 210 while parent is not None: |
| 211 if isinstance(parent, astroid.Decorators): |
| 212 return True |
| 213 if (parent.is_statement or |
| 214 isinstance(parent, astroid.Lambda) or |
| 215 isinstance(parent, (scoped_nodes.ComprehensionScope, |
| 216 scoped_nodes.ListComp))): |
| 217 break |
| 218 parent = parent.parent |
| 219 return False |
| 220 |
| 221 def is_ancestor_name(frame, node): |
| 222 """return True if `frame` is a astroid.Class node with `node` in the |
| 223 subtree of its bases attribute |
| 224 """ |
| 225 try: |
| 226 bases = frame.bases |
| 227 except AttributeError: |
| 228 return False |
| 229 for base in bases: |
| 230 if node in base.nodes_of_class(astroid.Name): |
| 231 return True |
| 232 return False |
| 233 |
| 234 def assign_parent(node): |
| 235 """return the higher parent which is not an AssName, Tuple or List node |
| 236 """ |
| 237 while node and isinstance(node, (astroid.AssName, |
| 238 astroid.Tuple, |
| 239 astroid.List)): |
| 240 node = node.parent |
| 241 return node |
| 242 |
| 243 def overrides_an_abstract_method(class_node, name): |
| 244 """return True if pnode is a parent of node""" |
| 245 for ancestor in class_node.ancestors(): |
| 246 if name in ancestor and isinstance(ancestor[name], astroid.Function) and
\ |
| 247 ancestor[name].is_abstract(pass_is_abstract=False): |
| 248 return True |
| 249 return False |
| 250 |
| 251 def overrides_a_method(class_node, name): |
| 252 """return True if <name> is a method overridden from an ancestor""" |
| 253 for ancestor in class_node.ancestors(): |
| 254 if name in ancestor and isinstance(ancestor[name], astroid.Function): |
| 255 return True |
| 256 return False |
| 257 |
| 258 PYMETHODS = set(('__new__', '__init__', '__del__', '__hash__', |
| 259 '__str__', '__repr__', |
| 260 '__len__', '__iter__', |
| 261 '__delete__', '__get__', '__set__', |
| 262 '__getitem__', '__setitem__', '__delitem__', '__contains__', |
| 263 '__getattribute__', '__getattr__', '__setattr__', '__delattr__'
, |
| 264 '__call__', |
| 265 '__enter__', '__exit__', |
| 266 '__cmp__', '__ge__', '__gt__', '__le__', '__lt__', '__eq__', |
| 267 '__nonzero__', '__neg__', '__invert__', |
| 268 '__mul__', '__imul__', '__rmul__', |
| 269 '__div__', '__idiv__', '__rdiv__', |
| 270 '__add__', '__iadd__', '__radd__', |
| 271 '__sub__', '__isub__', '__rsub__', |
| 272 '__pow__', '__ipow__', '__rpow__', |
| 273 '__mod__', '__imod__', '__rmod__', |
| 274 '__and__', '__iand__', '__rand__', |
| 275 '__or__', '__ior__', '__ror__', |
| 276 '__xor__', '__ixor__', '__rxor__', |
| 277 # XXX To be continued |
| 278 )) |
| 279 |
| 280 def check_messages(*messages): |
| 281 """decorator to store messages that are handled by a checker method""" |
| 282 |
| 283 def store_messages(func): |
| 284 func.checks_msgs = messages |
| 285 return func |
| 286 return store_messages |
| 287 |
| 288 class IncompleteFormatString(Exception): |
| 289 """A format string ended in the middle of a format specifier.""" |
| 290 pass |
| 291 |
| 292 class UnsupportedFormatCharacter(Exception): |
| 293 """A format character in a format string is not one of the supported |
| 294 format characters.""" |
| 295 def __init__(self, index): |
| 296 Exception.__init__(self, index) |
| 297 self.index = index |
| 298 |
| 299 def parse_format_string(format_string): |
| 300 """Parses a format string, returning a tuple of (keys, num_args), where keys |
| 301 is the set of mapping keys in the format string, and num_args is the number |
| 302 of arguments required by the format string. Raises |
| 303 IncompleteFormatString or UnsupportedFormatCharacter if a |
| 304 parse error occurs.""" |
| 305 keys = set() |
| 306 num_args = 0 |
| 307 def next_char(i): |
| 308 i += 1 |
| 309 if i == len(format_string): |
| 310 raise IncompleteFormatString |
| 311 return (i, format_string[i]) |
| 312 i = 0 |
| 313 while i < len(format_string): |
| 314 char = format_string[i] |
| 315 if char == '%': |
| 316 i, char = next_char(i) |
| 317 # Parse the mapping key (optional). |
| 318 key = None |
| 319 if char == '(': |
| 320 depth = 1 |
| 321 i, char = next_char(i) |
| 322 key_start = i |
| 323 while depth != 0: |
| 324 if char == '(': |
| 325 depth += 1 |
| 326 elif char == ')': |
| 327 depth -= 1 |
| 328 i, char = next_char(i) |
| 329 key_end = i - 1 |
| 330 key = format_string[key_start:key_end] |
| 331 |
| 332 # Parse the conversion flags (optional). |
| 333 while char in '#0- +': |
| 334 i, char = next_char(i) |
| 335 # Parse the minimum field width (optional). |
| 336 if char == '*': |
| 337 num_args += 1 |
| 338 i, char = next_char(i) |
| 339 else: |
| 340 while char in string.digits: |
| 341 i, char = next_char(i) |
| 342 # Parse the precision (optional). |
| 343 if char == '.': |
| 344 i, char = next_char(i) |
| 345 if char == '*': |
| 346 num_args += 1 |
| 347 i, char = next_char(i) |
| 348 else: |
| 349 while char in string.digits: |
| 350 i, char = next_char(i) |
| 351 # Parse the length modifier (optional). |
| 352 if char in 'hlL': |
| 353 i, char = next_char(i) |
| 354 # Parse the conversion type (mandatory). |
| 355 if PY3K: |
| 356 flags = 'diouxXeEfFgGcrs%a' |
| 357 else: |
| 358 flags = 'diouxXeEfFgGcrs%' |
| 359 if char not in flags: |
| 360 raise UnsupportedFormatCharacter(i) |
| 361 if key: |
| 362 keys.add(key) |
| 363 elif char != '%': |
| 364 num_args += 1 |
| 365 i += 1 |
| 366 return keys, num_args |
| 367 |
| 368 |
| 369 def is_attr_protected(attrname): |
| 370 """return True if attribute name is protected (start with _ and some other |
| 371 details), False otherwise. |
| 372 """ |
| 373 return attrname[0] == '_' and not attrname == '_' and not ( |
| 374 attrname.startswith('__') and attrname.endswith('__')) |
| 375 |
| 376 def node_frame_class(node): |
| 377 """return klass node for a method node (or a staticmethod or a |
| 378 classmethod), return null otherwise |
| 379 """ |
| 380 klass = node.frame() |
| 381 |
| 382 while klass is not None and not isinstance(klass, astroid.Class): |
| 383 if klass.parent is None: |
| 384 klass = None |
| 385 else: |
| 386 klass = klass.parent.frame() |
| 387 |
| 388 return klass |
| 389 |
| 390 def is_super_call(expr): |
| 391 """return True if expression node is a function call and if function name |
| 392 is super. Check before that you're in a method. |
| 393 """ |
| 394 return (isinstance(expr, astroid.CallFunc) and |
| 395 isinstance(expr.func, astroid.Name) and |
| 396 expr.func.name == 'super') |
| 397 |
| 398 def is_attr_private(attrname): |
| 399 """Check that attribute name is private (at least two leading underscores, |
| 400 at most one trailing underscore) |
| 401 """ |
| 402 regex = re.compile('^_{2,}.*[^_]+_?$') |
| 403 return regex.match(attrname) |
| 404 |
| 405 def get_argument_from_call(callfunc_node, position=None, keyword=None): |
| 406 """Returns the specified argument from a function call. |
| 407 |
| 408 :param callfunc_node: Node representing a function call to check. |
| 409 :param int position: position of the argument. |
| 410 :param str keyword: the keyword of the argument. |
| 411 |
| 412 :returns: The node representing the argument, None if the argument is not fo
und. |
| 413 :raises ValueError: if both position and keyword are None. |
| 414 :raises NoSuchArgumentError: if no argument at the provided position or with |
| 415 the provided keyword. |
| 416 """ |
| 417 if position is None and keyword is None: |
| 418 raise ValueError('Must specify at least one of: position or keyword.') |
| 419 try: |
| 420 if position is not None and not isinstance(callfunc_node.args[position],
astroid.Keyword): |
| 421 return callfunc_node.args[position] |
| 422 except IndexError as error: |
| 423 raise NoSuchArgumentError(error) |
| 424 if keyword: |
| 425 for arg in callfunc_node.args: |
| 426 if isinstance(arg, astroid.Keyword) and arg.arg == keyword: |
| 427 return arg.value |
| 428 raise NoSuchArgumentError |
| 429 |
| 430 def inherit_from_std_ex(node): |
| 431 """ |
| 432 Return true if the given class node is subclass of |
| 433 exceptions.Exception. |
| 434 """ |
| 435 if node.name in ('Exception', 'BaseException') \ |
| 436 and node.root().name == EXCEPTIONS_MODULE: |
| 437 return True |
| 438 return any(inherit_from_std_ex(parent) |
| 439 for parent in node.ancestors(recurs=False)) |
| 440 |
| 441 def is_import_error(handler): |
| 442 """ |
| 443 Check if the given exception handler catches |
| 444 ImportError. |
| 445 |
| 446 :param handler: A node, representing an ExceptHandler node. |
| 447 :returns: True if the handler catches ImportError, False otherwise. |
| 448 """ |
| 449 names = None |
| 450 if isinstance(handler.type, astroid.Tuple): |
| 451 names = [name for name in handler.type.elts |
| 452 if isinstance(name, astroid.Name)] |
| 453 elif isinstance(handler.type, astroid.Name): |
| 454 names = [handler.type] |
| 455 else: |
| 456 # Don't try to infer that. |
| 457 return |
| 458 for name in names: |
| 459 try: |
| 460 for infered in name.infer(): |
| 461 if (isinstance(infered, astroid.Class) and |
| 462 inherit_from_std_ex(infered) and |
| 463 infered.name == 'ImportError'): |
| 464 return True |
| 465 except astroid.InferenceError: |
| 466 continue |
| 467 |
| 468 def has_known_bases(klass): |
| 469 """Returns true if all base classes of a class could be inferred.""" |
| 470 try: |
| 471 return klass._all_bases_known |
| 472 except AttributeError: |
| 473 pass |
| 474 for base in klass.bases: |
| 475 result = safe_infer(base) |
| 476 # TODO: check for A->B->A->B pattern in class structure too? |
| 477 if (not isinstance(result, astroid.Class) or |
| 478 result is klass or |
| 479 not has_known_bases(result)): |
| 480 klass._all_bases_known = False |
| 481 return False |
| 482 klass._all_bases_known = True |
| 483 return True |
| 484 |
| 485 def decorated_with_property(node): |
| 486 """ Detect if the given function node is decorated with a property. """ |
| 487 if not node.decorators: |
| 488 return False |
| 489 for decorator in node.decorators.nodes: |
| 490 if not isinstance(decorator, astroid.Name): |
| 491 continue |
| 492 try: |
| 493 for infered in decorator.infer(): |
| 494 if isinstance(infered, astroid.Class): |
| 495 if (infered.root().name == BUILTINS_NAME and |
| 496 infered.name == 'property'): |
| 497 return True |
| 498 for ancestor in infered.ancestors(): |
| 499 if (ancestor.name == 'property' and |
| 500 ancestor.root().name == BUILTINS_NAME): |
| 501 return True |
| 502 except astroid.InferenceError: |
| 503 pass |
| 504 |
| 505 |
| 506 def decorated_with_abc(func): |
| 507 """Determine if the `func` node is decorated with `abc` decorators.""" |
| 508 if func.decorators: |
| 509 for node in func.decorators.nodes: |
| 510 try: |
| 511 infered = next(node.infer()) |
| 512 except astroid.InferenceError: |
| 513 continue |
| 514 if infered and infered.qname() in ABC_METHODS: |
| 515 return True |
| 516 |
| 517 |
| 518 def unimplemented_abstract_methods(node, is_abstract_cb=decorated_with_abc): |
| 519 """ |
| 520 Get the unimplemented abstract methods for the given *node*. |
| 521 |
| 522 A method can be considered abstract if the callback *is_abstract_cb* |
| 523 returns a ``True`` value. The check defaults to verifying that |
| 524 a method is decorated with abstract methods. |
| 525 The function will work only for new-style classes. For old-style |
| 526 classes, it will simply return an empty dictionary. |
| 527 For the rest of them, it will return a dictionary of abstract method |
| 528 names and their inferred objects. |
| 529 """ |
| 530 visited = {} |
| 531 try: |
| 532 mro = reversed(node.mro()) |
| 533 except NotImplementedError: |
| 534 # Old style class, it will not have a mro. |
| 535 return {} |
| 536 except astroid.ResolveError: |
| 537 # Probably inconsistent hierarchy, don'try |
| 538 # to figure this out here. |
| 539 return {} |
| 540 for ancestor in mro: |
| 541 for obj in ancestor.values(): |
| 542 infered = obj |
| 543 if isinstance(obj, astroid.AssName): |
| 544 infered = safe_infer(obj) |
| 545 if not infered: |
| 546 continue |
| 547 if not isinstance(infered, astroid.Function): |
| 548 if obj.name in visited: |
| 549 del visited[obj.name] |
| 550 if isinstance(infered, astroid.Function): |
| 551 # It's critical to use the original name, |
| 552 # since after inferring, an object can be something |
| 553 # else than expected, as in the case of the |
| 554 # following assignment. |
| 555 # |
| 556 # class A: |
| 557 # def keys(self): pass |
| 558 # __iter__ = keys |
| 559 abstract = is_abstract_cb(infered) |
| 560 if abstract: |
| 561 visited[obj.name] = infered |
| 562 elif not abstract and obj.name in visited: |
| 563 del visited[obj.name] |
| 564 return visited |
OLD | NEW |