OLD | NEW |
(Empty) | |
| 1 # pylint: disable=W0611 |
| 2 # |
| 3 # Copyright (c) 2003-2010 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 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 18 """some functions that may be useful for various checkers |
| 19 """ |
| 20 |
| 21 import string |
| 22 from logilab import astng |
| 23 from logilab.common.compat import builtins |
| 24 BUILTINS_NAME = builtins.__name__ |
| 25 |
| 26 COMP_NODE_TYPES = astng.ListComp, astng.SetComp, astng.DictComp, astng.GenExpr |
| 27 |
| 28 def is_inside_except(node): |
| 29 """Returns true if node is directly inside an exception handler""" |
| 30 return isinstance(node.parent, astng.ExceptHandler) |
| 31 |
| 32 |
| 33 def clobber_in_except(node): |
| 34 """Checks if an assignment node in an except handler clobbers an existing |
| 35 variable. |
| 36 |
| 37 Returns (True, args for W0623) if assignment clobbers an existing variable, |
| 38 (False, None) otherwise. |
| 39 """ |
| 40 if isinstance(node, astng.AssAttr): |
| 41 return (True, (node.attrname, 'object %r' % (node.expr.name,))) |
| 42 elif node is not None: |
| 43 name = node.name |
| 44 if is_builtin(name): |
| 45 return (True, (name, 'builtins')) |
| 46 else: |
| 47 scope, stmts = node.lookup(name) |
| 48 if (stmts and |
| 49 not isinstance(stmts[0].ass_type(), |
| 50 (astng.Assign, astng.AugAssign, astng.ExceptHandl
er))): |
| 51 return (True, (name, 'outer scope (line %i)' % (stmts[0].lineno,
))) |
| 52 return (False, None) |
| 53 |
| 54 |
| 55 def safe_infer(node): |
| 56 """return the inferred value for the given node. |
| 57 Return None if inference failed or if there is some ambiguity (more than |
| 58 one node has been inferred) |
| 59 """ |
| 60 try: |
| 61 inferit = node.infer() |
| 62 value = inferit.next() |
| 63 except astng.InferenceError: |
| 64 return |
| 65 try: |
| 66 inferit.next() |
| 67 return # None if there is ambiguity on the inferred node |
| 68 except StopIteration: |
| 69 return value |
| 70 |
| 71 def is_super(node): |
| 72 """return True if the node is referencing the "super" builtin function |
| 73 """ |
| 74 if getattr(node, 'name', None) == 'super' and \ |
| 75 node.root().name == BUILTINS_NAME: |
| 76 return True |
| 77 return False |
| 78 |
| 79 def is_error(node): |
| 80 """return true if the function does nothing but raising an exception""" |
| 81 for child_node in node.get_children(): |
| 82 if isinstance(child_node, astng.Raise): |
| 83 return True |
| 84 return False |
| 85 |
| 86 def is_raising(body): |
| 87 """return true if the given statement node raise an exception""" |
| 88 for node in body: |
| 89 if isinstance(node, astng.Raise): |
| 90 return True |
| 91 return False |
| 92 |
| 93 def is_empty(body): |
| 94 """return true if the given node does nothing but 'pass'""" |
| 95 return len(body) == 1 and isinstance(body[0], astng.Pass) |
| 96 |
| 97 builtins = __builtins__.copy() |
| 98 SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__') |
| 99 |
| 100 def is_builtin(name): # was is_native_builtin |
| 101 """return true if <name> could be considered as a builtin defined by python |
| 102 """ |
| 103 if name in builtins: |
| 104 return True |
| 105 if name in SPECIAL_BUILTINS: |
| 106 return True |
| 107 return False |
| 108 |
| 109 def is_defined_before(var_node): |
| 110 """return True if the variable node is defined by a parent node (list, |
| 111 set, dict, or generator comprehension, lambda) or in a previous sibling |
| 112 node on the same line (statement_defining ; statement_using) |
| 113 """ |
| 114 varname = var_node.name |
| 115 _node = var_node.parent |
| 116 while _node: |
| 117 if isinstance(_node, COMP_NODE_TYPES): |
| 118 for ass_node in _node.nodes_of_class(astng.AssName): |
| 119 if ass_node.name == varname: |
| 120 return True |
| 121 elif isinstance(_node, astng.For): |
| 122 for ass_node in _node.target.nodes_of_class(astng.AssName): |
| 123 if ass_node.name == varname: |
| 124 return True |
| 125 elif isinstance(_node, astng.With): |
| 126 if _node.vars is None: |
| 127 # quickfix : case in which 'with' is used without 'as' |
| 128 return False |
| 129 if _node.vars.name == varname: |
| 130 return True |
| 131 elif isinstance(_node, (astng.Lambda, astng.Function)): |
| 132 if _node.args.is_argument(varname): |
| 133 return True |
| 134 if getattr(_node, 'name', None) == varname: |
| 135 return True |
| 136 break |
| 137 _node = _node.parent |
| 138 # possibly multiple statements on the same line using semi colon separator |
| 139 stmt = var_node.statement() |
| 140 _node = stmt.previous_sibling() |
| 141 lineno = stmt.fromlineno |
| 142 while _node and _node.fromlineno == lineno: |
| 143 for ass_node in _node.nodes_of_class(astng.AssName): |
| 144 if ass_node.name == varname: |
| 145 return True |
| 146 for imp_node in _node.nodes_of_class( (astng.From, astng.Import)): |
| 147 if varname in [name[1] or name[0] for name in imp_node.names]: |
| 148 return True |
| 149 _node = _node.previous_sibling() |
| 150 return False |
| 151 |
| 152 def is_func_default(node): |
| 153 """return true if the given Name node is used in function default argument's |
| 154 value |
| 155 """ |
| 156 parent = node.scope() |
| 157 if isinstance(parent, astng.Function): |
| 158 for default_node in parent.args.defaults: |
| 159 for default_name_node in default_node.nodes_of_class(astng.Name): |
| 160 if default_name_node is node: |
| 161 return True |
| 162 return False |
| 163 |
| 164 def is_func_decorator(node): |
| 165 """return true if the name is used in function decorator""" |
| 166 parent = node.parent |
| 167 while parent is not None: |
| 168 if isinstance(parent, astng.Decorators): |
| 169 return True |
| 170 if parent.is_statement or isinstance(parent, astng.Lambda): |
| 171 break |
| 172 parent = parent.parent |
| 173 return False |
| 174 |
| 175 def is_ancestor_name(frame, node): |
| 176 """return True if `frame` is a astng.Class node with `node` in the |
| 177 subtree of its bases attribute |
| 178 """ |
| 179 try: |
| 180 bases = frame.bases |
| 181 except AttributeError: |
| 182 return False |
| 183 for base in bases: |
| 184 if node in base.nodes_of_class(astng.Name): |
| 185 return True |
| 186 return False |
| 187 |
| 188 def assign_parent(node): |
| 189 """return the higher parent which is not an AssName, Tuple or List node |
| 190 """ |
| 191 while node and isinstance(node, (astng.AssName, |
| 192 astng.Tuple, |
| 193 astng.List)): |
| 194 node = node.parent |
| 195 return node |
| 196 |
| 197 def overrides_an_abstract_method(class_node, name): |
| 198 """return True if pnode is a parent of node""" |
| 199 for ancestor in class_node.ancestors(): |
| 200 if name in ancestor and isinstance(ancestor[name], astng.Function) and \ |
| 201 ancestor[name].is_abstract(pass_is_abstract=False): |
| 202 return True |
| 203 return False |
| 204 |
| 205 def overrides_a_method(class_node, name): |
| 206 """return True if <name> is a method overridden from an ancestor""" |
| 207 for ancestor in class_node.ancestors(): |
| 208 if name in ancestor and isinstance(ancestor[name], astng.Function): |
| 209 return True |
| 210 return False |
| 211 |
| 212 PYMETHODS = set(('__new__', '__init__', '__del__', '__hash__', |
| 213 '__str__', '__repr__', |
| 214 '__len__', '__iter__', |
| 215 '__delete__', '__get__', '__set__', |
| 216 '__getitem__', '__setitem__', '__delitem__', '__contains__', |
| 217 '__getattribute__', '__getattr__', '__setattr__', '__delattr__'
, |
| 218 '__call__', |
| 219 '__enter__', '__exit__', |
| 220 '__cmp__', '__ge__', '__gt__', '__le__', '__lt__', '__eq__', |
| 221 '__nonzero__', '__neg__', '__invert__', |
| 222 '__mul__', '__imul__', '__rmul__', |
| 223 '__div__', '__idiv__', '__rdiv__', |
| 224 '__add__', '__iadd__', '__radd__', |
| 225 '__sub__', '__isub__', '__rsub__', |
| 226 '__pow__', '__ipow__', '__rpow__', |
| 227 '__mod__', '__imod__', '__rmod__', |
| 228 '__and__', '__iand__', '__rand__', |
| 229 '__or__', '__ior__', '__ror__', |
| 230 '__xor__', '__ixor__', '__rxor__', |
| 231 # XXX To be continued |
| 232 )) |
| 233 |
| 234 def check_messages(*messages): |
| 235 """decorator to store messages that are handled by a checker method""" |
| 236 |
| 237 def store_messages(func): |
| 238 func.checks_msgs = messages |
| 239 return func |
| 240 return store_messages |
| 241 |
| 242 class IncompleteFormatString(Exception): |
| 243 """A format string ended in the middle of a format specifier.""" |
| 244 pass |
| 245 |
| 246 class UnsupportedFormatCharacter(Exception): |
| 247 """A format character in a format string is not one of the supported |
| 248 format characters.""" |
| 249 def __init__(self, index): |
| 250 Exception.__init__(self, index) |
| 251 self.index = index |
| 252 |
| 253 def parse_format_string(format_string): |
| 254 """Parses a format string, returning a tuple of (keys, num_args), where keys |
| 255 is the set of mapping keys in the format string, and num_args is the number |
| 256 of arguments required by the format string. Raises |
| 257 IncompleteFormatString or UnsupportedFormatCharacter if a |
| 258 parse error occurs.""" |
| 259 keys = set() |
| 260 num_args = 0 |
| 261 def next_char(i): |
| 262 i += 1 |
| 263 if i == len(format_string): |
| 264 raise IncompleteFormatString |
| 265 return (i, format_string[i]) |
| 266 i = 0 |
| 267 while i < len(format_string): |
| 268 c = format_string[i] |
| 269 if c == '%': |
| 270 i, c = next_char(i) |
| 271 # Parse the mapping key (optional). |
| 272 key = None |
| 273 if c == '(': |
| 274 depth = 1 |
| 275 i, c = next_char(i) |
| 276 key_start = i |
| 277 while depth != 0: |
| 278 if c == '(': |
| 279 depth += 1 |
| 280 elif c == ')': |
| 281 depth -= 1 |
| 282 i, c = next_char(i) |
| 283 key_end = i - 1 |
| 284 key = format_string[key_start:key_end] |
| 285 |
| 286 # Parse the conversion flags (optional). |
| 287 while c in '#0- +': |
| 288 i, c = next_char(i) |
| 289 # Parse the minimum field width (optional). |
| 290 if c == '*': |
| 291 num_args += 1 |
| 292 i, c = next_char(i) |
| 293 else: |
| 294 while c in string.digits: |
| 295 i, c = next_char(i) |
| 296 # Parse the precision (optional). |
| 297 if c == '.': |
| 298 i, c = next_char(i) |
| 299 if c == '*': |
| 300 num_args += 1 |
| 301 i, c = next_char(i) |
| 302 else: |
| 303 while c in string.digits: |
| 304 i, c = next_char(i) |
| 305 # Parse the length modifier (optional). |
| 306 if c in 'hlL': |
| 307 i, c = next_char(i) |
| 308 # Parse the conversion type (mandatory). |
| 309 if c not in 'diouxXeEfFgGcrs%': |
| 310 raise UnsupportedFormatCharacter(i) |
| 311 if key: |
| 312 keys.add(key) |
| 313 elif c != '%': |
| 314 num_args += 1 |
| 315 i += 1 |
| 316 return keys, num_args |
OLD | NEW |