1# -*- coding: utf-8 -*- 2"""Checker of PEP-8 Naming Conventions.""" 3import sys 4from collections import deque 5from fnmatch import fnmatch 6from functools import partial 7from itertools import chain 8 9from flake8 import style_guide 10from flake8_polyfill import options 11 12try: 13 import ast 14 from ast import iter_child_nodes 15except ImportError: 16 from flake8.util import ast, iter_child_nodes 17 18__version__ = '0.12.1' 19 20PYTHON_VERSION = sys.version_info[:3] 21PY2 = PYTHON_VERSION[0] == 2 22 23CLASS_METHODS = frozenset(( 24 '__new__', 25 '__init_subclass__', 26 '__class_getitem__', 27)) 28METACLASS_BASES = frozenset(('type', 'ABCMeta')) 29 30# Node types which may contain class methods 31METHOD_CONTAINER_NODES = {ast.If, ast.While, ast.For, ast.With} 32FUNC_NODES = (ast.FunctionDef,) 33 34if PY2: 35 METHOD_CONTAINER_NODES |= {ast.TryExcept, ast.TryFinally} 36else: 37 METHOD_CONTAINER_NODES |= {ast.Try} 38 39if PYTHON_VERSION > (3, 5): 40 FUNC_NODES += (ast.AsyncFunctionDef,) 41 METHOD_CONTAINER_NODES |= {ast.AsyncWith, ast.AsyncFor} 42 43if PY2: 44 def _unpack_args(args): 45 ret = [] 46 for arg in args: 47 if isinstance(arg, ast.Tuple): 48 ret.extend(_unpack_args(arg.elts)) 49 else: 50 ret.append((arg, arg.id)) 51 return ret 52 53 def get_arg_name_tuples(node): 54 return _unpack_args(node.args.args) 55elif PYTHON_VERSION < (3, 8): 56 def get_arg_name_tuples(node): 57 groups = (node.args.args, node.args.kwonlyargs) 58 return [(arg, arg.arg) for args in groups for arg in args] 59else: 60 def get_arg_name_tuples(node): 61 groups = (node.args.posonlyargs, node.args.args, node.args.kwonlyargs) 62 return [(arg, arg.arg) for args in groups for arg in args] 63 64 65class _ASTCheckMeta(type): 66 def __init__(cls, class_name, bases, namespace): 67 cls.codes = tuple(code for code in namespace if code.startswith('N')) 68 try: 69 cls.all.append(cls()) 70 except AttributeError: 71 cls.all = [] 72 73 74def _err(self, node, code, **kwargs): 75 lineno, col_offset = node.lineno, node.col_offset 76 if isinstance(node, ast.ClassDef): 77 if PYTHON_VERSION < (3, 8): 78 lineno += len(node.decorator_list) 79 col_offset += 6 80 elif isinstance(node, FUNC_NODES): 81 if PYTHON_VERSION < (3, 8): 82 lineno += len(node.decorator_list) 83 col_offset += 4 84 code_str = getattr(self, code) 85 if kwargs: 86 code_str = code_str.format(**kwargs) 87 return lineno, col_offset + 1, '%s %s' % (code, code_str), self 88 89 90def _ignored(name, ignore): 91 return any(fnmatch(name, i) for i in ignore) 92 93 94BaseASTCheck = _ASTCheckMeta('BaseASTCheck', (object,), 95 {'__doc__': "Base for AST Checks.", 'err': _err}) 96 97 98class _FunctionType(object): 99 CLASSMETHOD = 'classmethod' 100 STATICMETHOD = 'staticmethod' 101 FUNCTION = 'function' 102 METHOD = 'method' 103 104 105_default_ignore_names = [ 106 'setUp', 107 'tearDown', 108 'setUpClass', 109 'tearDownClass', 110 'asyncSetUp', 111 'asyncTearDown', 112 'setUpTestData', 113 'failureException', 114 'longMessage', 115 'maxDiff'] 116_default_classmethod_decorators = ['classmethod'] 117_default_staticmethod_decorators = ['staticmethod'] 118 119 120def _build_decorator_to_type(classmethod_decorators, staticmethod_decorators): 121 decorator_to_type = {} 122 for decorator in classmethod_decorators: 123 decorator_to_type[decorator] = _FunctionType.CLASSMETHOD 124 for decorator in staticmethod_decorators: 125 decorator_to_type[decorator] = _FunctionType.STATICMETHOD 126 return decorator_to_type 127 128 129class NamingChecker(object): 130 """Checker of PEP-8 Naming Conventions.""" 131 name = 'naming' 132 version = __version__ 133 visitors = BaseASTCheck.all 134 decorator_to_type = _build_decorator_to_type( 135 _default_classmethod_decorators, _default_staticmethod_decorators) 136 ignore_names = frozenset(_default_ignore_names) 137 138 def __init__(self, tree, filename): 139 self.parents = deque() 140 self._node = tree 141 142 @classmethod 143 def add_options(cls, parser): 144 options.register(parser, '--ignore-names', 145 default=_default_ignore_names, 146 action='store', 147 type='string', 148 parse_from_config=True, 149 comma_separated_list=True, 150 help='List of names or glob patterns the pep8-naming ' 151 'plugin should ignore. (Defaults to %default)') 152 153 options.register(parser, '--classmethod-decorators', 154 default=_default_classmethod_decorators, 155 action='store', 156 type='string', 157 parse_from_config=True, 158 comma_separated_list=True, 159 help='List of method decorators pep8-naming plugin ' 160 'should consider classmethods (Defaults to ' 161 '%default)') 162 163 options.register(parser, '--staticmethod-decorators', 164 default=_default_staticmethod_decorators, 165 action='store', 166 type='string', 167 parse_from_config=True, 168 comma_separated_list=True, 169 help='List of method decorators pep8-naming plugin ' 170 'should consider staticmethods (Defaults to ' 171 '%default)') 172 parser.extend_default_ignore(['N818']) 173 174 @classmethod 175 def parse_options(cls, options): 176 cls.ignore_names = frozenset(options.ignore_names) 177 cls.decorator_to_type = _build_decorator_to_type( 178 options.classmethod_decorators, 179 options.staticmethod_decorators) 180 181 # Build a list of node visitors based the error codes that have been 182 # selected in the style guide. Only the checks that have been selected 183 # will be evaluated as a performance optimization. 184 engine = style_guide.DecisionEngine(options) 185 cls.visitors = frozenset( 186 visitor for visitor in BaseASTCheck.all for code in visitor.codes 187 if engine.decision_for(code) is style_guide.Decision.Selected 188 ) 189 190 def run(self): 191 return self.visit_tree(self._node) if self._node else () 192 193 def visit_tree(self, node): 194 for error in self.visit_node(node): 195 yield error 196 self.parents.append(node) 197 for child in iter_child_nodes(node): 198 for error in self.visit_tree(child): 199 yield error 200 self.parents.pop() 201 202 def visit_node(self, node): 203 if isinstance(node, ast.ClassDef): 204 self.tag_class_functions(node) 205 elif isinstance(node, FUNC_NODES): 206 self.find_global_defs(node) 207 208 method = 'visit_' + node.__class__.__name__.lower() 209 parents = self.parents 210 ignore_names = self.ignore_names 211 for visitor in self.visitors: 212 visitor_method = getattr(visitor, method, None) 213 if visitor_method is None: 214 continue 215 for error in visitor_method(node, parents, ignore_names): 216 yield error 217 218 def tag_class_functions(self, cls_node): 219 """Tag functions if they are methods, classmethods, staticmethods""" 220 # tries to find all 'old style decorators' like 221 # m = staticmethod(m) 222 late_decoration = {} 223 for node in iter_child_nodes(cls_node): 224 if not (isinstance(node, ast.Assign) and 225 isinstance(node.value, ast.Call) and 226 isinstance(node.value.func, ast.Name)): 227 continue 228 func_name = node.value.func.id 229 if func_name not in self.decorator_to_type: 230 continue 231 meth = (len(node.value.args) == 1 and node.value.args[0]) 232 if isinstance(meth, ast.Name): 233 late_decoration[meth.id] = self.decorator_to_type[func_name] 234 235 # If this class inherits from a known metaclass base class, it is 236 # itself a metaclass, and we'll consider all of its methods to be 237 # classmethods. 238 bases = chain( 239 (b.id for b in cls_node.bases if isinstance(b, ast.Name)), 240 (b.attr for b in cls_node.bases if isinstance(b, ast.Attribute)), 241 ) 242 ismetaclass = any(name for name in bases if name in METACLASS_BASES) 243 244 self.set_function_nodes_types( 245 iter_child_nodes(cls_node), ismetaclass, late_decoration) 246 247 def set_function_nodes_types(self, nodes, ismetaclass, late_decoration): 248 # iterate over all functions and tag them 249 for node in nodes: 250 if type(node) in METHOD_CONTAINER_NODES: 251 self.set_function_nodes_types( 252 iter_child_nodes(node), ismetaclass, late_decoration) 253 if not isinstance(node, FUNC_NODES): 254 continue 255 node.function_type = _FunctionType.METHOD 256 if node.name in CLASS_METHODS or ismetaclass: 257 node.function_type = _FunctionType.CLASSMETHOD 258 if node.name in late_decoration: 259 node.function_type = late_decoration[node.name] 260 elif node.decorator_list: 261 for d in node.decorator_list: 262 name = self.find_decorator_name(d) 263 if name in self.decorator_to_type: 264 node.function_type = self.decorator_to_type[name] 265 break 266 267 @classmethod 268 def find_decorator_name(cls, d): 269 if isinstance(d, ast.Name): 270 return d.id 271 elif isinstance(d, ast.Attribute): 272 return d.attr 273 elif isinstance(d, ast.Call): 274 return cls.find_decorator_name(d.func) 275 276 @staticmethod 277 def find_global_defs(func_def_node): 278 global_names = set() 279 nodes_to_check = deque(iter_child_nodes(func_def_node)) 280 while nodes_to_check: 281 node = nodes_to_check.pop() 282 if isinstance(node, ast.Global): 283 global_names.update(node.names) 284 285 if not isinstance(node, (ast.ClassDef,) + FUNC_NODES): 286 nodes_to_check.extend(iter_child_nodes(node)) 287 func_def_node.global_names = global_names 288 289 290class ClassNameCheck(BaseASTCheck): 291 """ 292 Almost without exception, class names use the CapWords convention. 293 294 Classes for internal use have a leading underscore in addition. 295 """ 296 N801 = "class name '{name}' should use CapWords convention" 297 N818 = "exception name '{name}' should be named with an Error suffix" 298 299 @classmethod 300 def get_classdef(cls, name, parents): 301 for parent in parents: 302 for node in parent.body: 303 if isinstance(node, ast.ClassDef) and node.name == name: 304 return node 305 306 @classmethod 307 def superclass_names(cls, name, parents, _names=None): 308 names = _names or set() 309 classdef = cls.get_classdef(name, parents) 310 if not classdef: 311 return names 312 for base in classdef.bases: 313 if isinstance(base, ast.Name) and base.id not in names: 314 names.add(base.id) 315 names.update(cls.superclass_names(base.id, parents, names)) 316 return names 317 318 def visit_classdef(self, node, parents, ignore=None): 319 name = node.name 320 if _ignored(name, ignore): 321 return 322 name = name.strip('_') 323 if not name[:1].isupper() or '_' in name: 324 yield self.err(node, 'N801', name=name) 325 superclasses = self.superclass_names(name, parents) 326 if "Exception" in superclasses and not name.endswith("Error"): 327 yield self.err(node, 'N818', name=name) 328 329 330class FunctionNameCheck(BaseASTCheck): 331 """ 332 Function names should be lowercase, with words separated by underscores 333 as necessary to improve readability. 334 335 Functions *not* being methods '__' in front and back are not allowed. 336 337 mixedCase is allowed only in contexts where that's already the 338 prevailing style (e.g. threading.py), to retain backwards compatibility. 339 """ 340 N802 = "function name '{name}' should be lowercase" 341 N807 = "function name '{name}' should not start and end with '__'" 342 343 def visit_functiondef(self, node, parents, ignore=None): 344 function_type = getattr(node, 'function_type', _FunctionType.FUNCTION) 345 name = node.name 346 if _ignored(name, ignore): 347 return 348 if name in ('__dir__', '__getattr__'): 349 return 350 if name.lower() != name: 351 yield self.err(node, 'N802', name=name) 352 if (function_type == _FunctionType.FUNCTION 353 and name[:2] == '__' and name[-2:] == '__'): 354 yield self.err(node, 'N807', name=name) 355 356 visit_asyncfunctiondef = visit_functiondef 357 358 359class FunctionArgNamesCheck(BaseASTCheck): 360 """ 361 The argument names of a function should be lowercase, with words separated 362 by underscores. 363 364 A classmethod should have 'cls' as first argument. 365 A method should have 'self' as first argument. 366 """ 367 N803 = "argument name '{name}' should be lowercase" 368 N804 = "first argument of a classmethod should be named 'cls'" 369 N805 = "first argument of a method should be named 'self'" 370 371 def visit_functiondef(self, node, parents, ignore=None): 372 373 def arg_name(arg): 374 try: 375 return arg, arg.arg 376 except AttributeError: # PY2 377 return node, arg 378 379 for arg, name in arg_name(node.args.vararg), arg_name(node.args.kwarg): 380 if name is None or _ignored(name, ignore): 381 continue 382 if name.lower() != name: 383 yield self.err(arg, 'N803', name=name) 384 return 385 386 arg_name_tuples = get_arg_name_tuples(node) 387 if not arg_name_tuples: 388 return 389 arg0, name0 = arg_name_tuples[0] 390 function_type = getattr(node, 'function_type', _FunctionType.FUNCTION) 391 392 if function_type == _FunctionType.METHOD: 393 if name0 != 'self' and not _ignored(name0, ignore): 394 yield self.err(arg0, 'N805') 395 elif function_type == _FunctionType.CLASSMETHOD: 396 if name0 != 'cls' and not _ignored(name0, ignore): 397 yield self.err(arg0, 'N804') 398 for arg, name in arg_name_tuples: 399 if name.lower() != name and not _ignored(name, ignore): 400 yield self.err(arg, 'N803', name=name) 401 return 402 403 visit_asyncfunctiondef = visit_functiondef 404 405 406class ImportAsCheck(BaseASTCheck): 407 """ 408 Don't change the naming convention via an import 409 """ 410 N811 = "constant '{name}' imported as non constant '{asname}'" 411 N812 = "lowercase '{name}' imported as non lowercase '{asname}'" 412 N813 = "camelcase '{name}' imported as lowercase '{asname}'" 413 N814 = "camelcase '{name}' imported as constant '{asname}'" 414 N817 = "camelcase '{name}' imported as acronym '{asname}'" 415 416 def visit_importfrom(self, node, parents, ignore=None): 417 for name in node.names: 418 asname = name.asname 419 if not asname: 420 continue 421 original_name = name.name 422 err_kwargs = {'name': original_name, 'asname': asname} 423 if original_name.isupper(): 424 if not asname.isupper(): 425 yield self.err(node, 'N811', **err_kwargs) 426 elif original_name.islower(): 427 if asname.lower() != asname: 428 yield self.err(node, 'N812', **err_kwargs) 429 elif asname.islower(): 430 yield self.err(node, 'N813', **err_kwargs) 431 elif asname.isupper(): 432 if ''.join(filter(str.isupper, original_name)) == asname: 433 yield self.err(node, 'N817', **err_kwargs) 434 else: 435 yield self.err(node, 'N814', **err_kwargs) 436 437 visit_import = visit_importfrom 438 439 440class VariablesCheck(BaseASTCheck): 441 """ 442 Class attributes and local variables in functions should be lowercase 443 """ 444 N806 = "variable '{name}' in function should be lowercase" 445 N815 = "variable '{name}' in class scope should not be mixedCase" 446 N816 = "variable '{name}' in global scope should not be mixedCase" 447 448 def _find_errors(self, assignment_target, parents, ignore): 449 for parent_func in reversed(parents): 450 if isinstance(parent_func, ast.ClassDef): 451 checker = self.class_variable_check 452 break 453 if isinstance(parent_func, FUNC_NODES): 454 checker = partial(self.function_variable_check, parent_func) 455 break 456 else: 457 checker = self.global_variable_check 458 for name in _extract_names(assignment_target): 459 if _ignored(name, ignore): 460 continue 461 error_code = checker(name) 462 if error_code: 463 yield self.err(assignment_target, error_code, name=name) 464 465 @staticmethod 466 def is_namedtupe(node_value): 467 if isinstance(node_value, ast.Call): 468 if isinstance(node_value.func, ast.Attribute): 469 if node_value.func.attr == 'namedtuple': 470 return True 471 elif isinstance(node_value.func, ast.Name): 472 if node_value.func.id == 'namedtuple': 473 return True 474 return False 475 476 def visit_assign(self, node, parents, ignore=None): 477 if self.is_namedtupe(node.value): 478 return 479 for target in node.targets: 480 for error in self._find_errors(target, parents, ignore): 481 yield error 482 483 def visit_namedexpr(self, node, parents, ignore): 484 if self.is_namedtupe(node.value): 485 return 486 for error in self._find_errors(node.target, parents, ignore): 487 yield error 488 489 visit_annassign = visit_namedexpr 490 491 def visit_with(self, node, parents, ignore): 492 if PY2: 493 for error in self._find_errors( 494 node.optional_vars, parents, ignore): 495 yield error 496 return 497 for item in node.items: 498 for error in self._find_errors( 499 item.optional_vars, parents, ignore): 500 yield error 501 502 visit_asyncwith = visit_with 503 504 def visit_for(self, node, parents, ignore): 505 for error in self._find_errors(node.target, parents, ignore): 506 yield error 507 508 visit_asyncfor = visit_for 509 510 def visit_excepthandler(self, node, parents, ignore): 511 if node.name: 512 for error in self._find_errors(node, parents, ignore): 513 yield error 514 515 def visit_generatorexp(self, node, parents, ignore): 516 for gen in node.generators: 517 for error in self._find_errors(gen.target, parents, ignore): 518 yield error 519 520 visit_listcomp = visit_dictcomp = visit_setcomp = visit_generatorexp 521 522 @staticmethod 523 def global_variable_check(name): 524 if is_mixed_case(name): 525 return 'N816' 526 527 @staticmethod 528 def class_variable_check(name): 529 if is_mixed_case(name): 530 return 'N815' 531 532 @staticmethod 533 def function_variable_check(func, var_name): 534 if var_name in func.global_names: 535 return None 536 if var_name.lower() == var_name: 537 return None 538 return 'N806' 539 540 541def _extract_names(assignment_target): 542 """Yield assignment_target ids.""" 543 target_type = type(assignment_target) 544 if target_type is ast.Name: 545 yield assignment_target.id 546 elif target_type in (ast.Tuple, ast.List): 547 for element in assignment_target.elts: 548 element_type = type(element) 549 if element_type is ast.Name: 550 yield element.id 551 elif element_type in (ast.Tuple, ast.List): 552 for n in _extract_names(element): 553 yield n 554 elif not PY2 and element_type is ast.Starred: # PEP 3132 555 for n in _extract_names(element.value): 556 yield n 557 elif target_type is ast.ExceptHandler: 558 if PY2: 559 # Python 2 supports unpacking tuple exception values. 560 if isinstance(assignment_target.name, ast.Tuple): 561 for name in assignment_target.name.elts: 562 yield name.id 563 elif isinstance(assignment_target.name, ast.Attribute): 564 # Python 2 also supports assigning an exception to an attribute 565 # eg. except Exception as obj.attr 566 yield assignment_target.name.attr 567 else: 568 yield assignment_target.name.id 569 else: 570 yield assignment_target.name 571 572 573def is_mixed_case(name): 574 return name.lower() != name and name.lstrip('_')[:1].islower() 575