src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Naming/NamingStyleChecker.py

branch
eric7
changeset 9271
f655c20ff500
parent 9221
bf71ee032bb4
child 9653
e67609152c5e
equal deleted inserted replaced
9270:2f60384fe01f 9271:f655c20ff500
5 5
6 """ 6 """
7 Module implementing a checker for naming conventions. 7 Module implementing a checker for naming conventions.
8 """ 8 """
9 9
10 import ast
10 import collections 11 import collections
11 import ast 12 import functools
12 import re
13 import os 13 import os
14 14
15 try: 15 try:
16 ast.AsyncFunctionDef # __IGNORE_EXCEPTION__ 16 ast.AsyncFunctionDef # __IGNORE_EXCEPTION__
17 except AttributeError: 17 except AttributeError:
20 20
21 class NamingStyleChecker: 21 class NamingStyleChecker:
22 """ 22 """
23 Class implementing a checker for naming conventions. 23 Class implementing a checker for naming conventions.
24 """ 24 """
25
26 LowercaseRegex = re.compile(r"[_a-z][_a-z0-9]*$")
27 UppercaseRegexp = re.compile(r"[_A-Z][_A-Z0-9]*$")
28 CamelcaseRegexp = re.compile(r"_?[A-Z][a-zA-Z0-9]*$")
29 MixedcaseRegexp = re.compile(r"_?[a-z][a-zA-Z0-9]*$")
30 25
31 Codes = [ 26 Codes = [
32 "N801", 27 "N801",
33 "N802", 28 "N802",
34 "N803", 29 "N803",
35 "N804", 30 "N804",
36 "N805", 31 "N805",
37 "N806", 32 "N806",
38 "N807", 33 "N807",
39 "N808", 34 "N808",
35 "N809",
40 "N811", 36 "N811",
41 "N812", 37 "N812",
42 "N813", 38 "N813",
43 "N814", 39 "N814",
40 "N815",
41 "N818",
44 "N821", 42 "N821",
43 "N822",
44 "N823",
45 "N831", 45 "N831",
46 ] 46 ]
47 47
48 def __init__(self, tree, filename, options): 48 def __init__(self, tree, filename, options):
49 """ 49 """
57 self.__tree = tree 57 self.__tree = tree
58 self.__filename = filename 58 self.__filename = filename
59 59
60 self.__checkersWithCodes = { 60 self.__checkersWithCodes = {
61 "classdef": [ 61 "classdef": [
62 (self.__checkClassName, ("N801",)), 62 (self.__checkClassName, ("N801", "N818")),
63 (self.__checkNameToBeAvoided, ("N831",)), 63 (self.__checkNameToBeAvoided, ("N831",)),
64 ],
65 "functiondef": [
66 (self.__checkFunctionName, ("N802",)),
67 (self.__checkFunctionArgumentNames, ("N803", "N804", "N805", "N806")),
68 (self.__checkNameToBeAvoided, ("N831",)),
69 ],
70 "assign": [
71 (self.__checkVariablesInFunction, ("N821",)),
72 (self.__checkNameToBeAvoided, ("N831",)),
73 ],
74 "importfrom": [
75 (self.__checkImportAs, ("N811", "N812", "N813", "N814")),
76 ], 64 ],
77 "module": [ 65 "module": [
78 (self.__checkModule, ("N807", "N808")), 66 (self.__checkModule, ("N807", "N808")),
79 ], 67 ],
80 } 68 }
69 for name in ("functiondef", "asyncfunctiondef"):
70 self.__checkersWithCodes[name] = [
71 (self.__checkFunctionName, ("N802", "N809")),
72 (self.__checkFunctionArgumentNames, ("N803", "N804", "N805", "N806")),
73 (self.__checkNameToBeAvoided, ("N831",)),
74 ]
75 for name in ("assign", "namedexpr", "annassign"):
76 self.__checkersWithCodes[name] = [
77 (self.__checkVariableNames, ("N821",)),
78 (self.__checkNameToBeAvoided, ("N831",)),
79 ]
80 for name in (
81 "with",
82 "asyncwith",
83 "for",
84 "asyncfor",
85 "excepthandler",
86 "generatorexp",
87 "listcomp",
88 "dictcomp",
89 "setcomp",
90 ):
91 self.__checkersWithCodes[name] = [
92 (self.__checkVariableNames, ("N821",)),
93 ]
94 for name in ("import", "importfrom"):
95 self.__checkersWithCodes[name] = [
96 (self.__checkImportAs, ("N811", "N812", "N813", "N814", "N815")),
97 ]
81 98
82 self.__checkers = {} 99 self.__checkers = {}
83 for key, checkers in self.__checkersWithCodes.items(): 100 for key, checkers in self.__checkersWithCodes.items():
84 for checker, codes in checkers: 101 for checker, codes in checkers:
85 if any(not (code and options.ignore_code(code)) for code in codes): 102 if any(not (code and options.ignore_code(code)) for code in codes):
249 """ 266 """
250 if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): 267 if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
251 name = node.name 268 name = node.name
252 if self.__isNameToBeAvoided(name): 269 if self.__isNameToBeAvoided(name):
253 yield self.__error(node, "N831") 270 yield self.__error(node, "N831")
254 return 271
255 272 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
256 if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
257 argNames = self.__getArgNames(node) 273 argNames = self.__getArgNames(node)
258 for arg in argNames: 274 for arg in argNames:
259 if self.__isNameToBeAvoided(arg): 275 if self.__isNameToBeAvoided(arg):
260 yield self.__error(node, "N831") 276 yield self.__error(node, "N831")
261 return 277
262 278 elif isinstance(node, (ast.Assign, ast.NamedExpr, ast.AnnAssign)):
263 if isinstance(node, ast.Assign): 279 if isinstance(node, ast.Assign):
264 for target in node.targets: 280 targets = node.targets
281 else:
282 targets = [node.target]
283 for target in targets:
265 if isinstance(target, ast.Name): 284 if isinstance(target, ast.Name):
266 name = target.id 285 name = target.id
267 if not name: 286 if bool(name) and self.__isNameToBeAvoided(name):
268 return
269
270 if self.__isNameToBeAvoided(name):
271 yield self.__error(node, "N831") 287 yield self.__error(node, "N831")
272 return
273 288
274 elif isinstance(target, (ast.Tuple, ast.List)): 289 elif isinstance(target, (ast.Tuple, ast.List)):
275 for element in target.elts: 290 for element in target.elts:
276 if isinstance(element, ast.Name): 291 if isinstance(element, ast.Name):
277 name = element.id 292 name = element.id
278 if not name: 293 if bool(name) and self.__isNameToBeAvoided(name):
279 return
280
281 if self.__isNameToBeAvoided(name):
282 yield self.__error(node, "N831") 294 yield self.__error(node, "N831")
283 return 295
296 def __getClassdef(self, name, parents):
297 """
298 Private method to extract the class definition.
299
300 @param name name of the class
301 @type str
302 @param parents list of parent nodes
303 @type ast.AST
304 @return node containing the class definition
305 @rtype ast.ClassDef
306 """
307 for parent in parents:
308 for node in parent.body:
309 if isinstance(node, ast.ClassDef) and node.name == name:
310 return node
311
312 return None
313
314 def __superClassNames(self, name, parents, names=None):
315 """
316 Private method to extract the names of all super classes.
317
318 @param name name of the class
319 @type str
320 @param parents list of parent nodes
321 @type ast.AST
322 @param names set of collected class names (defaults to None)
323 @type set of str (optional)
324 @return set of class names
325 @rtype set of str
326 """
327 if names is None:
328 # initialize recursive search with empty set
329 names = set()
330
331 classdef = self.__getClassdef(name, parents)
332 if not classdef:
333 return names
334
335 for base in classdef.bases:
336 if isinstance(base, ast.Name) and base.id not in names:
337 names.add(base.id)
338 names.update(self.__superClassNames(base.id, parents, names))
339 return names
284 340
285 def __checkClassName(self, node, parents): 341 def __checkClassName(self, node, parents):
286 """ 342 """
287 Private class to check the given node for class name 343 Private class to check the given node for class name
288 conventions (N801). 344 conventions (N801, N818).
289 345
290 Almost without exception, class names use the CapWords convention. 346 Almost without exception, class names use the CapWords convention.
291 Classes for internal use have a leading underscore in addition. 347 Classes for internal use have a leading underscore in addition.
292 348
293 @param node AST note to check 349 @param node AST note to check
294 @param parents list of parent nodes 350 @param parents list of parent nodes
295 @yield tuple giving line number, offset within line and error code 351 @yield tuple giving line number, offset within line and error code
296 @ytype tuple of (int, int, str) 352 @ytype tuple of (int, int, str)
297 """ 353 """
298 if not self.CamelcaseRegexp.match(node.name): 354 name = node.name
355 strippedName = name.strip("_")
356 if not strippedName[:1].isupper() or "_" in strippedName:
299 yield self.__error(node, "N801") 357 yield self.__error(node, "N801")
300 358
359 superClasses = self.__superClassNames(name, parents)
360 if "Exception" in superClasses and not name.endswith("Error"):
361 yield self.__error(node, "N818")
362
301 def __checkFunctionName(self, node, parents): 363 def __checkFunctionName(self, node, parents):
302 """ 364 """
303 Private class to check the given node for function name 365 Private class to check the given node for function name
304 conventions (N802). 366 conventions (N802, N809).
305 367
306 Function names should be lowercase, with words separated by underscores 368 Function names should be lowercase, with words separated by underscores
307 as necessary to improve readability. Functions <b>not</b> being 369 as necessary to improve readability. Functions <b>not</b> being
308 methods '__' in front and back are not allowed. Mixed case is allowed 370 methods '__' in front and back are not allowed. Mixed case is allowed
309 only in contexts where that's already the prevailing style 371 only in contexts where that's already the prevailing style
314 @yield tuple giving line number, offset within line and error code 376 @yield tuple giving line number, offset within line and error code
315 @ytype tuple of (int, int, str) 377 @ytype tuple of (int, int, str)
316 """ 378 """
317 functionType = getattr(node, "function_type", "function") 379 functionType = getattr(node, "function_type", "function")
318 name = node.name 380 name = node.name
319 if ( 381
320 functionType == "function" and "__" in (name[:2], name[-2:]) 382 if name in ("__dir__", "__getattr__"):
321 ) or not self.LowercaseRegex.match(name): 383 return
384
385 if name.lower() != name:
322 yield self.__error(node, "N802") 386 yield self.__error(node, "N802")
387 if functionType == "function" and name[:2] == "__" and name[-2:] == "__":
388 yield self.__error(node, "N809")
323 389
324 def __checkFunctionArgumentNames(self, node, parents): 390 def __checkFunctionArgumentNames(self, node, parents):
325 """ 391 """
326 Private class to check the argument names of functions 392 Private class to check the argument names of functions
327 (N803, N804, N805, N806). 393 (N803, N804, N805, N806).
335 @yield tuple giving line number, offset within line and error code 401 @yield tuple giving line number, offset within line and error code
336 @ytype tuple of (int, int, str) 402 @ytype tuple of (int, int, str)
337 """ 403 """
338 if node.args.kwarg is not None: 404 if node.args.kwarg is not None:
339 kwarg = node.args.kwarg.arg 405 kwarg = node.args.kwarg.arg
340 if not self.LowercaseRegex.match(kwarg): 406 if kwarg.lower() != kwarg:
341 yield self.__error(node, "N803") 407 yield self.__error(node, "N803")
408
409 elif node.args.vararg is not None:
410 vararg = node.args.vararg.arg
411 if vararg.lower() != vararg:
412 yield self.__error(node, "N803")
413
414 else:
415 argNames = self.__getArgNames(node)
416 functionType = getattr(node, "function_type", "function")
417
418 if not argNames:
419 if functionType == "method":
420 yield self.__error(node, "N805")
421 elif functionType == "classmethod":
422 yield self.__error(node, "N804")
423
424 elif functionType == "method" and argNames[0] != "self":
425 yield self.__error(node, "N805")
426 elif functionType == "classmethod" and argNames[0] != "cls":
427 yield self.__error(node, "N804")
428 elif functionType == "staticmethod" and argNames[0] in ("cls", "self"):
429 yield self.__error(node, "N806")
430 for arg in argNames:
431 if arg.lower() != arg:
432 yield self.__error(node, "N803")
433 break
434
435 def __checkVariableNames(self, node, parents):
436 """
437 Private method to check variable names in function, class and global scope
438 (N821, N822, N823).
439
440 Local variables in functions should be lowercase.
441
442 @param node AST note to check
443 @param parents list of parent nodes
444 @yield tuple giving line number, offset within line and error code
445 @ytype tuple of (int, int, str)
446 """
447 nodeType = type(node)
448 if nodeType is ast.Assign:
449 if self.__isNamedTupel(node.value):
342 return 450 return
343 451 for target in node.targets:
344 if node.args.vararg is not None: 452 yield from self.__findVariableNameErrors(target, parents)
345 vararg = node.args.vararg.arg 453
346 if not self.LowercaseRegex.match(vararg): 454 elif nodeType in (ast.NamedExpr, ast.AnnAssign):
347 yield self.__error(node, "N803") 455 if self.__isNamedTupel(node.value):
348 return 456 return
349 457 yield from self.__findVariableNameErrors(node.target, parents)
350 argNames = self.__getArgNames(node) 458
351 functionType = getattr(node, "function_type", "function") 459 elif nodeType in (ast.With, ast.AsyncWith):
352 460 for item in node.items:
353 if not argNames: 461 yield from self.__findVariableNameErrors(item.optional_vars, parents)
354 if functionType == "method": 462
355 yield self.__error(node, "N805") 463 elif nodeType in (ast.For, ast.AsyncFor):
356 elif functionType == "classmethod": 464 yield from self.__findVariableNameErrors(node.target, parents)
357 yield self.__error(node, "N804") 465
358 return 466 elif nodeType is ast.ExceptHandler:
359 467 if node.name:
360 if functionType == "method" and argNames[0] != "self": 468 yield from self.__findVariableNameErrors(node, parents)
361 yield self.__error(node, "N805") 469
362 elif functionType == "classmethod" and argNames[0] != "cls": 470 elif nodeType in (ast.GeneratorExp, ast.ListComp, ast.DictComp, ast.SetComp):
363 yield self.__error(node, "N804") 471 for gen in node.generators:
364 elif functionType == "staticmethod" and argNames[0] in ("cls", "self"): 472 yield from self.__findVariableNameErrors(gen.target, parents)
365 yield self.__error(node, "N806") 473
366 for arg in argNames: 474 def __findVariableNameErrors(self, assignmentTarget, parents):
367 if not self.LowercaseRegex.match(arg): 475 """
368 yield self.__error(node, "N803") 476 Private method to check, if there is a variable name error.
369 return 477
370 478 @param assignmentTarget target node of the assignment
371 def __checkVariablesInFunction(self, node, parents): 479 @type ast.Name, ast.Tuple, ast.List or ast.ExceptHandler
372 """ 480 @param parents list of parent nodes
373 Private method to check local variables in functions (N821). 481 @type ast.AST
374
375 Local variables in functions should be lowercase.
376
377 @param node AST note to check
378 @param parents list of parent nodes
379 @yield tuple giving line number, offset within line and error code 482 @yield tuple giving line number, offset within line and error code
380 @ytype tuple of (int, int, str) 483 @ytype tuple of (int, int, str)
381 """ 484 """
382 for parentFunc in reversed(parents): 485 for parentFunc in reversed(parents):
383 if isinstance(parentFunc, ast.ClassDef): 486 if isinstance(parentFunc, ast.ClassDef):
384 return 487 checker = self.__classVariableCheck
488 break
385 if isinstance(parentFunc, (ast.FunctionDef, ast.AsyncFunctionDef)): 489 if isinstance(parentFunc, (ast.FunctionDef, ast.AsyncFunctionDef)):
490 checker = functools.partial(self.__functionVariableCheck, parentFunc)
386 break 491 break
387 else: 492 else:
388 return 493 checker = self.__globalVariableCheck
389 for target in node.targets: 494 for name in self.__extractNames(assignmentTarget):
390 name = isinstance(target, ast.Name) and target.id 495 errorCode = checker(name)
391 if not name or name in parentFunc.global_names: 496 if errorCode:
392 return 497 yield self.__error(assignmentTarget, errorCode)
393 498
394 if not self.LowercaseRegex.match(name) and name[:1] != "_": 499 def __extractNames(self, assignmentTarget):
395 yield self.__error(target, "N821") 500 """
501 Private method to extract the names from the target node.
502
503 @param assignmentTarget target node of the assignment
504 @type ast.Name, ast.Tuple, ast.List or ast.ExceptHandler
505 @yield name of the variable
506 @ytype str
507 """
508 targetType = type(assignmentTarget)
509 if targetType is ast.Name:
510 yield assignmentTarget.id
511 elif targetType in (ast.Tuple, ast.List):
512 for element in assignmentTarget.elts:
513 elementType = type(element)
514 if elementType is ast.Name:
515 yield element.id
516 elif elementType in (ast.Tuple, ast.List):
517 yield from self.__extractNames(element)
518 elif elementType is ast.Starred: # PEP 3132
519 yield from self.__extractNames(element.value)
520 elif isinstance(assignmentTarget, ast.ExceptHandler):
521 yield assignmentTarget.name
522
523 def __isMixedCase(self, name):
524 """
525 Private method to check, if the given name is mixed case.
526
527 @param name variable name to be checked
528 @type str
529 @return flag indicating mixed case
530 @rtype bool
531 """
532 return name.lower() != name and name.lstrip("_")[:1].islower()
533
534 def __globalVariableCheck(self, name):
535 """
536 Private method to determine the error code for a variable in global scope.
537
538 @param name variable name to be checked
539 @type str
540 @return error code or None
541 @rtype str or None
542 """
543 if self.__isMixedCase(name):
544 return "N823"
545
546 return None
547
548 def __classVariableCheck(self, name):
549 """
550 Private method to determine the error code for a variable in class scope.
551
552 @param name variable name to be checked
553 @type str
554 @return error code or None
555 @rtype str or None
556 """
557 if self.__isMixedCase(name):
558 return "N822"
559
560 return None
561
562 def __functionVariableCheck(self, func, varName):
563 """
564 Private method to determine the error code for a variable in class scope.
565
566 @param func reference to the function definition node
567 @type ast.FunctionDef or ast.AsyncFunctionDef
568 @param varName variable name to be checked
569 @type str
570 @return error code or None
571 @rtype str or None
572 """
573 if varName not in func.global_names and varName.lower() != varName:
574 return "N821"
575
576 return None
577
578 def __isNamedTupel(self, nodeValue):
579 """
580 Private method to check, if a node is a named tuple.
581
582 @param nodeValue node to be checked
583 @type ast.AST
584 @return flag indicating a nemd tuple
585 @rtype bool
586 """
587 return isinstance(nodeValue, ast.Call) and (
588 (
589 isinstance(nodeValue.func, ast.Attribute)
590 and nodeValue.func.attr == "namedtuple"
591 )
592 or (
593 isinstance(nodeValue.func, ast.Name)
594 and nodeValue.func.id == "namedtuple"
595 )
596 )
396 597
397 def __checkModule(self, node, parents): 598 def __checkModule(self, node, parents):
398 """ 599 """
399 Private method to check module naming conventions (N807, N808). 600 Private method to check module naming conventions (N807, N808).
400 601
417 yield self.__error(node, "N808") 618 yield self.__error(node, "N808")
418 619
419 def __checkImportAs(self, node, parents): 620 def __checkImportAs(self, node, parents):
420 """ 621 """
421 Private method to check that imports don't change the 622 Private method to check that imports don't change the
422 naming convention (N811, N812, N813, N814). 623 naming convention (N811, N812, N813, N814, N815).
423 624
424 @param node AST note to check 625 @param node AST note to check
425 @param parents list of parent nodes 626 @param parents list of parent nodes
426 @yield tuple giving line number, offset within line and error code 627 @yield tuple giving line number, offset within line and error code
427 @ytype tuple of (int, int, str) 628 @ytype tuple of (int, int, str)
428 """ 629 """
429 for name in node.names: 630 for name in node.names:
430 if not name.asname: 631 asname = name.asname
632 if not asname:
431 continue 633 continue
432 634
433 if self.UppercaseRegexp.match(name.name): 635 originalName = name.name
434 if not self.UppercaseRegexp.match(name.asname): 636 if originalName.isupper():
637 if not asname.isupper():
435 yield self.__error(node, "N811") 638 yield self.__error(node, "N811")
436 elif self.LowercaseRegex.match(name.name): 639 elif originalName.islower():
437 if not self.LowercaseRegex.match(name.asname): 640 if asname.lower() != asname:
438 yield self.__error(node, "N812") 641 yield self.__error(node, "N812")
439 elif self.LowercaseRegex.match(name.asname): 642 elif asname.islower():
440 yield self.__error(node, "N813") 643 yield self.__error(node, "N813")
441 elif self.UppercaseRegexp.match(name.asname): 644 elif asname.isupper():
442 yield self.__error(node, "N814") 645 if "".join(filter(str.isupper, originalName)) == asname:
646 yield self.__error(node, "N815")
647 else:
648 yield self.__error(node, "N814")

eric ide

mercurial