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

branch
eric7
changeset 11142
2f0fb22c1d63
parent 11090
f5f5f5803935
child 11145
d328a7b74fd8
equal deleted inserted replaced
11141:2f5f73c51c7c 11142:2f0fb22c1d63
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 ast
11 import collections 11 import collections
12 import copy
12 import functools 13 import functools
13 import os 14 import os
14 15
15 try: 16 try:
16 ast.AsyncFunctionDef # __IGNORE_EXCEPTION__ 17 ast.AsyncFunctionDef # __IGNORE_EXCEPTION__
17 except AttributeError: 18 except AttributeError:
18 ast.AsyncFunctionDef = ast.FunctionDef 19 ast.AsyncFunctionDef = ast.FunctionDef
19 20
20 21
22 # TODO: change this to a checker like the others
21 class NamingStyleChecker: 23 class NamingStyleChecker:
22 """ 24 """
23 Class implementing a checker for naming conventions. 25 Class implementing a checker for naming conventions.
24 """ 26 """
25 27
43 "N822", 45 "N822",
44 "N823", 46 "N823",
45 "N831", 47 "N831",
46 ] 48 ]
47 49
48 def __init__(self, tree, filename, options): 50 def __init__(self, source, filename, tree, select, ignore, expected, repeat, args):
49 """ 51 """
50 Constructor (according to 'extended' pycodestyle.py API) 52 Constructor
51 53
52 @param tree AST tree of the source file 54 @param source source code to be checked
53 @type ast.AST 55 @type list of str
54 @param filename name of the source file 56 @param filename name of the source file
55 @type str 57 @type str
56 @param options options as parsed by pycodestyle.StyleGuide 58 @param tree AST tree of the source code
57 @type optparse.Option 59 @type ast.Module
58 """ 60 @param select list of selected codes
61 @type list of str
62 @param ignore list of codes to be ignored
63 @type list of str
64 @param expected list of expected codes
65 @type list of str
66 @param repeat flag indicating to report each occurrence of a code
67 @type bool
68 @param args dictionary of arguments for the various checks
69 @type dict
70 """
71 self.__select = tuple(select)
72 self.__ignore = tuple(ignore)
73 self.__expected = expected[:]
74 self.__repeat = repeat
75 self.__filename = filename
76 self.__source = source[:]
77 self.__tree = copy.deepcopy(tree)
78 self.__args = args
79
59 self.__parents = collections.deque() 80 self.__parents = collections.deque()
60 self.__tree = tree 81
61 self.__filename = filename 82 # statistics counters
83 self.counters = {}
84
85 # collection of detected errors
86 self.errors = []
62 87
63 self.__checkersWithCodes = { 88 self.__checkersWithCodes = {
64 "classdef": [ 89 "classdef": [
65 (self.__checkClassName, ("N801", "N818")), 90 (self.__checkClassName, ("N801", "N818")),
66 (self.__checkNameToBeAvoided, ("N831",)), 91 (self.__checkNameToBeAvoided, ("N831",)),
97 for name in ("import", "importfrom"): 122 for name in ("import", "importfrom"):
98 self.__checkersWithCodes[name] = [ 123 self.__checkersWithCodes[name] = [
99 (self.__checkImportAs, ("N811", "N812", "N813", "N814", "N815")), 124 (self.__checkImportAs, ("N811", "N812", "N813", "N814", "N815")),
100 ] 125 ]
101 126
102 self.__checkers = {} 127 self.__checkers = collections.defaultdict(list)
103 for key, checkers in self.__checkersWithCodes.items(): 128 for key, checkers in self.__checkersWithCodes.items():
104 for checker, codes in checkers: 129 for checker, codes in checkers:
105 if any(not (code and options.ignore_code(code)) for code in codes): 130 if any(not (code and self.__ignoreCode(code)) for code in codes):
106 if key not in self.__checkers:
107 self.__checkers[key] = []
108 self.__checkers[key].append(checker) 131 self.__checkers[key].append(checker)
132
133 def __ignoreCode(self, code):
134 """
135 Private method to check if the message code should be ignored.
136
137 @param code message code to check for
138 @type str
139 @return flag indicating to ignore the given code
140 @rtype bool
141 """
142 return (
143 code in self.__ignore
144 or (code.startswith(self.__ignore) and not code.startswith(self.__select))
145 )
146
147 def __error(self, node, code):
148 """
149 Private method to build the error information.
150
151 @param node AST node to report an error for
152 @type ast.AST
153 @param code error code to report
154 @type str
155 """
156 if self.__ignoreCode(code):
157 return
158
159 if isinstance(node, ast.Module):
160 lineno = 0
161 offset = 0
162 else:
163 lineno = node.lineno
164 offset = node.col_offset
165 if isinstance(node, ast.ClassDef):
166 lineno += len(node.decorator_list)
167 offset += 6
168 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
169 lineno += len(node.decorator_list)
170 offset += 4
171
172 # record the issue with one based line number
173 errorInfo = {
174 "file": self.__filename,
175 "line": lineno,
176 "offset": offset,
177 "code": code,
178 "args": [],
179 }
180
181 if errorInfo not in self.errors:
182 # this issue was not seen before
183 if code in self.counters:
184 self.counters[code] += 1
185 else:
186 self.counters[code] = 1
187
188 # Don't care about expected codes
189 if code in self.__expected:
190 return
191
192 if code and (self.counters[code] == 1 or self.__repeat):
193 self.errors.append(errorInfo)
109 194
110 def run(self): 195 def run(self):
111 """ 196 """
112 Public method run by the pycodestyle.py checker. 197 Public method run by the pycodestyle.py checker.
113 198
124 """ 209 """
125 Private method to scan the given AST tree. 210 Private method to scan the given AST tree.
126 211
127 @param node AST tree node to scan 212 @param node AST tree node to scan
128 @type ast.AST 213 @type ast.AST
129 @yield tuple giving line number, offset within line and error code 214 """
130 @ytype tuple of (int, int, str) 215 self.__visitNode(node)
131 """
132 yield from self.__visitNode(node)
133 self.__parents.append(node) 216 self.__parents.append(node)
134 for child in ast.iter_child_nodes(node): 217 for child in ast.iter_child_nodes(node):
135 yield from self.__visitTree(child) 218 self.__visitTree(child)
136 self.__parents.pop() 219 self.__parents.pop()
137 220
138 def __visitNode(self, node): 221 def __visitNode(self, node):
139 """ 222 """
140 Private method to inspect the given AST node. 223 Private method to inspect the given AST node.
141 224
142 @param node AST tree node to inspect 225 @param node AST tree node to inspect
143 @type ast.AST 226 @type ast.AST
144 @yield tuple giving line number, offset within line and error code
145 @ytype tuple of (int, int, str)
146 """ 227 """
147 if isinstance(node, ast.ClassDef): 228 if isinstance(node, ast.ClassDef):
148 self.__tagClassFunctions(node) 229 self.__tagClassFunctions(node)
149 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): 230 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
150 self.__findGlobalDefs(node) 231 self.__findGlobalDefs(node)
151 232
152 checkerName = node.__class__.__name__.lower() 233 checkerName = node.__class__.__name__.lower()
153 if checkerName in self.__checkers: 234 if checkerName in self.__checkers:
154 for checker in self.__checkers[checkerName]: 235 for checker in self.__checkers[checkerName]:
155 for error in checker(node, self.__parents): 236 checker(node, self.__parents)
156 yield error + (self.__checkers[checkerName],)
157 237
158 def __tagClassFunctions(self, classNode): 238 def __tagClassFunctions(self, classNode):
159 """ 239 """
160 Private method to tag functions if they are methods, class methods or 240 Private method to tag functions if they are methods, class methods or
161 static methods. 241 static methods.
231 """ 311 """
232 posArgs = [arg.arg for arg in node.args.args] 312 posArgs = [arg.arg for arg in node.args.args]
233 kwOnly = [arg.arg for arg in node.args.kwonlyargs] 313 kwOnly = [arg.arg for arg in node.args.kwonlyargs]
234 return posArgs + kwOnly 314 return posArgs + kwOnly
235 315
236 def __error(self, node, code):
237 """
238 Private method to build the error information.
239
240 @param node AST node to report an error for
241 @type ast.AST
242 @param code error code to report
243 @type str
244 @return tuple giving line number, offset within line and error code
245 @rtype tuple of (int, int, str)
246 """
247 if isinstance(node, ast.Module):
248 lineno = 0
249 offset = 0
250 else:
251 lineno = node.lineno
252 offset = node.col_offset
253 if isinstance(node, ast.ClassDef):
254 lineno += len(node.decorator_list)
255 offset += 6
256 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
257 lineno += len(node.decorator_list)
258 offset += 4
259 return (lineno, offset, code)
260
261 def __isNameToBeAvoided(self, name): 316 def __isNameToBeAvoided(self, name):
262 """ 317 """
263 Private method to check, if the given name should be avoided. 318 Private method to check, if the given name should be avoided.
264 319
265 @param name name to be checked 320 @param name name to be checked
275 330
276 @param node AST note to check 331 @param node AST note to check
277 @type ast.Ast 332 @type ast.Ast
278 @param _parents list of parent nodes (unused) 333 @param _parents list of parent nodes (unused)
279 @type list of ast.AST 334 @type list of ast.AST
280 @yield tuple giving line number, offset within line and error code
281 @ytype tuple of (int, int, str)
282 """ 335 """
283 if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): 336 if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
284 name = node.name 337 name = node.name
285 if self.__isNameToBeAvoided(name): 338 if self.__isNameToBeAvoided(name):
286 yield self.__error(node, "N831") 339 self.__error(node, "N831")
287 340
288 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): 341 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
289 argNames = self.__getArgNames(node) 342 argNames = self.__getArgNames(node)
290 for arg in argNames: 343 for arg in argNames:
291 if self.__isNameToBeAvoided(arg): 344 if self.__isNameToBeAvoided(arg):
292 yield self.__error(node, "N831") 345 self.__error(node, "N831")
293 346
294 elif isinstance(node, (ast.Assign, ast.NamedExpr, ast.AnnAssign)): 347 elif isinstance(node, (ast.Assign, ast.NamedExpr, ast.AnnAssign)):
295 if isinstance(node, ast.Assign): 348 if isinstance(node, ast.Assign):
296 targets = node.targets 349 targets = node.targets
297 else: 350 else:
298 targets = [node.target] 351 targets = [node.target]
299 for target in targets: 352 for target in targets:
300 if isinstance(target, ast.Name): 353 if isinstance(target, ast.Name):
301 name = target.id 354 name = target.id
302 if bool(name) and self.__isNameToBeAvoided(name): 355 if bool(name) and self.__isNameToBeAvoided(name):
303 yield self.__error(node, "N831") 356 self.__error(node, "N831")
304 357
305 elif isinstance(target, (ast.Tuple, ast.List)): 358 elif isinstance(target, (ast.Tuple, ast.List)):
306 for element in target.elts: 359 for element in target.elts:
307 if isinstance(element, ast.Name): 360 if isinstance(element, ast.Name):
308 name = element.id 361 name = element.id
309 if bool(name) and self.__isNameToBeAvoided(name): 362 if bool(name) and self.__isNameToBeAvoided(name):
310 yield self.__error(node, "N831") 363 self.__error(node, "N831")
311 364
312 def __getClassdef(self, name, parents): 365 def __getClassdef(self, name, parents):
313 """ 366 """
314 Private method to extract the class definition. 367 Private method to extract the class definition.
315 368
364 417
365 @param node AST note to check 418 @param node AST note to check
366 @type ast.ClassDef 419 @type ast.ClassDef
367 @param parents list of parent nodes 420 @param parents list of parent nodes
368 @type list of ast.AST 421 @type list of ast.AST
369 @yield tuple giving line number, offset within line and error code
370 @ytype tuple of (int, int, str)
371 """ 422 """
372 name = node.name 423 name = node.name
373 strippedName = name.strip("_") 424 strippedName = name.strip("_")
374 if not strippedName[:1].isupper() or "_" in strippedName: 425 if not strippedName[:1].isupper() or "_" in strippedName:
375 yield self.__error(node, "N801") 426 self.__error(node, "N801")
376 427
377 superClasses = self.__superClassNames(name, parents) 428 superClasses = self.__superClassNames(name, parents)
378 if "Exception" in superClasses and not name.endswith("Error"): 429 if "Exception" in superClasses and not name.endswith("Error"):
379 yield self.__error(node, "N818") 430 self.__error(node, "N818")
380 431
381 def __checkFunctionName(self, node, _parents): 432 def __checkFunctionName(self, node, _parents):
382 """ 433 """
383 Private class to check the given node for function name 434 Private class to check the given node for function name
384 conventions (N802, N809). 435 conventions (N802, N809).
391 442
392 @param node AST note to check 443 @param node AST note to check
393 @type ast.FunctionDef or ast.AsynFunctionDef 444 @type ast.FunctionDef or ast.AsynFunctionDef
394 @param _parents list of parent nodes (unused) 445 @param _parents list of parent nodes (unused)
395 @type list of ast.AST 446 @type list of ast.AST
396 @yield tuple giving line number, offset within line and error code
397 @ytype tuple of (int, int, str)
398 """ 447 """
399 functionType = getattr(node, "function_type", "function") 448 functionType = getattr(node, "function_type", "function")
400 name = node.name 449 name = node.name
401 450
402 if name in ("__dir__", "__getattr__"): 451 if name in ("__dir__", "__getattr__"):
403 return 452 return
404 453
405 if name.lower() != name: 454 if name.lower() != name:
406 yield self.__error(node, "N802") 455 self.__error(node, "N802")
407 if functionType == "function" and name[:2] == "__" and name[-2:] == "__": 456 if functionType == "function" and name[:2] == "__" and name[-2:] == "__":
408 yield self.__error(node, "N809") 457 self.__error(node, "N809")
409 458
410 def __checkFunctionArgumentNames(self, node, _parents): 459 def __checkFunctionArgumentNames(self, node, _parents):
411 """ 460 """
412 Private class to check the argument names of functions 461 Private class to check the argument names of functions
413 (N803, N804, N805, N806). 462 (N803, N804, N805, N806).
418 467
419 @param node AST note to check 468 @param node AST note to check
420 @type ast.FunctionDef or ast.AsynFunctionDef 469 @type ast.FunctionDef or ast.AsynFunctionDef
421 @param _parents list of parent nodes (unused) 470 @param _parents list of parent nodes (unused)
422 @type list of ast.AST 471 @type list of ast.AST
423 @yield tuple giving line number, offset within line and error code
424 @ytype tuple of (int, int, str)
425 """ 472 """
426 if node.args.kwarg is not None: 473 if node.args.kwarg is not None:
427 kwarg = node.args.kwarg.arg 474 kwarg = node.args.kwarg.arg
428 if kwarg.lower() != kwarg: 475 if kwarg.lower() != kwarg:
429 yield self.__error(node, "N803") 476 self.__error(node, "N803")
430 477
431 elif node.args.vararg is not None: 478 elif node.args.vararg is not None:
432 vararg = node.args.vararg.arg 479 vararg = node.args.vararg.arg
433 if vararg.lower() != vararg: 480 if vararg.lower() != vararg:
434 yield self.__error(node, "N803") 481 self.__error(node, "N803")
435 482
436 else: 483 else:
437 argNames = self.__getArgNames(node) 484 argNames = self.__getArgNames(node)
438 functionType = getattr(node, "function_type", "function") 485 functionType = getattr(node, "function_type", "function")
439 486
440 if not argNames: 487 if not argNames:
441 if functionType == "method": 488 if functionType == "method":
442 yield self.__error(node, "N805") 489 self.__error(node, "N805")
443 elif functionType == "classmethod": 490 elif functionType == "classmethod":
444 yield self.__error(node, "N804") 491 self.__error(node, "N804")
445 492
446 elif functionType == "method" and argNames[0] != "self": 493 elif functionType == "method" and argNames[0] != "self":
447 yield self.__error(node, "N805") 494 self.__error(node, "N805")
448 elif functionType == "classmethod" and argNames[0] != "cls": 495 elif functionType == "classmethod" and argNames[0] != "cls":
449 yield self.__error(node, "N804") 496 self.__error(node, "N804")
450 elif functionType == "staticmethod" and argNames[0] in ("cls", "self"): 497 elif functionType == "staticmethod" and argNames[0] in ("cls", "self"):
451 yield self.__error(node, "N806") 498 self.__error(node, "N806")
452 for arg in argNames: 499 for arg in argNames:
453 if arg.lower() != arg: 500 if arg.lower() != arg:
454 yield self.__error(node, "N803") 501 self.__error(node, "N803")
455 break 502 break
456 503
457 def __checkVariableNames(self, node, parents): 504 def __checkVariableNames(self, node, parents):
458 """ 505 """
459 Private method to check variable names in function, class and global scope 506 Private method to check variable names in function, class and global scope
463 510
464 @param node AST note to check 511 @param node AST note to check
465 @type ast.AST 512 @type ast.AST
466 @param parents list of parent nodes 513 @param parents list of parent nodes
467 @type list of ast.AST 514 @type list of ast.AST
468 @yield tuple giving line number, offset within line and error code
469 @ytype tuple of (int, int, str)
470 """ 515 """
471 nodeType = type(node) 516 nodeType = type(node)
472 if nodeType is ast.Assign: 517 if nodeType is ast.Assign:
473 if self.__isNamedTupel(node.value): 518 if self.__isNamedTupel(node.value):
474 return 519 return
475 for target in node.targets: 520 for target in node.targets:
476 yield from self.__findVariableNameErrors(target, parents) 521 self.__findVariableNameErrors(target, parents)
477 522
478 elif nodeType in (ast.NamedExpr, ast.AnnAssign): 523 elif nodeType in (ast.NamedExpr, ast.AnnAssign):
479 if self.__isNamedTupel(node.value): 524 if self.__isNamedTupel(node.value):
480 return 525 return
481 yield from self.__findVariableNameErrors(node.target, parents) 526 self.__findVariableNameErrors(node.target, parents)
482 527
483 elif nodeType in (ast.With, ast.AsyncWith): 528 elif nodeType in (ast.With, ast.AsyncWith):
484 for item in node.items: 529 for item in node.items:
485 yield from self.__findVariableNameErrors(item.optional_vars, parents) 530 self.__findVariableNameErrors(item.optional_vars, parents)
486 531
487 elif nodeType in (ast.For, ast.AsyncFor): 532 elif nodeType in (ast.For, ast.AsyncFor):
488 yield from self.__findVariableNameErrors(node.target, parents) 533 self.__findVariableNameErrors(node.target, parents)
489 534
490 elif nodeType is ast.ExceptHandler: 535 elif nodeType is ast.ExceptHandler:
491 if node.name: 536 if node.name:
492 yield from self.__findVariableNameErrors(node, parents) 537 self.__findVariableNameErrors(node, parents)
493 538
494 elif nodeType in (ast.GeneratorExp, ast.ListComp, ast.DictComp, ast.SetComp): 539 elif nodeType in (ast.GeneratorExp, ast.ListComp, ast.DictComp, ast.SetComp):
495 for gen in node.generators: 540 for gen in node.generators:
496 yield from self.__findVariableNameErrors(gen.target, parents) 541 self.__findVariableNameErrors(gen.target, parents)
497 542
498 def __findVariableNameErrors(self, assignmentTarget, parents): 543 def __findVariableNameErrors(self, assignmentTarget, parents):
499 """ 544 """
500 Private method to check, if there is a variable name error. 545 Private method to check, if there is a variable name error.
501 546
502 @param assignmentTarget target node of the assignment 547 @param assignmentTarget target node of the assignment
503 @type ast.Name, ast.Tuple, ast.List or ast.ExceptHandler 548 @type ast.Name, ast.Tuple, ast.List or ast.ExceptHandler
504 @param parents list of parent nodes 549 @param parents list of parent nodes
505 @type ast.AST 550 @type ast.AST
506 @yield tuple giving line number, offset within line and error code
507 @ytype tuple of (int, int, str)
508 """ 551 """
509 for parentFunc in reversed(parents): 552 for parentFunc in reversed(parents):
510 if isinstance(parentFunc, ast.ClassDef): 553 if isinstance(parentFunc, ast.ClassDef):
511 checker = self.__classVariableCheck 554 checker = self.__classVariableCheck
512 break 555 break
516 else: 559 else:
517 checker = self.__globalVariableCheck 560 checker = self.__globalVariableCheck
518 for name in self.__extractNames(assignmentTarget): 561 for name in self.__extractNames(assignmentTarget):
519 errorCode = checker(name) 562 errorCode = checker(name)
520 if errorCode: 563 if errorCode:
521 yield self.__error(assignmentTarget, errorCode) 564 self.__error(assignmentTarget, errorCode)
522 565
523 def __extractNames(self, assignmentTarget): 566 def __extractNames(self, assignmentTarget):
524 """ 567 """
525 Private method to extract the names from the target node. 568 Private method to extract the names from the target node.
526 569
627 670
628 @param node AST node to check 671 @param node AST node to check
629 @type ast.AST 672 @type ast.AST
630 @param _parents list of parent nodes (unused) 673 @param _parents list of parent nodes (unused)
631 @type list of ast.AST 674 @type list of ast.AST
632 @yield tuple giving line number, offset within line and error code
633 @ytype tuple of (int, int, str)
634 """ 675 """
635 if self.__filename: 676 if self.__filename:
636 moduleName = os.path.splitext(os.path.basename(self.__filename))[0] 677 moduleName = os.path.splitext(os.path.basename(self.__filename))[0]
637 if moduleName.lower() != moduleName: 678 if moduleName.lower() != moduleName:
638 yield self.__error(node, "N807") 679 self.__error(node, "N807")
639 680
640 if moduleName == "__init__": 681 if moduleName == "__init__":
641 # we got a package 682 # we got a package
642 packageName = os.path.split(os.path.dirname(self.__filename))[1] 683 packageName = os.path.split(os.path.dirname(self.__filename))[1]
643 if packageName.lower() != packageName: 684 if packageName.lower() != packageName:
644 yield self.__error(node, "N808") 685 self.__error(node, "N808")
645 686
646 def __checkImportAs(self, node, _parents): 687 def __checkImportAs(self, node, _parents):
647 """ 688 """
648 Private method to check that imports don't change the 689 Private method to check that imports don't change the
649 naming convention (N811, N812, N813, N814, N815). 690 naming convention (N811, N812, N813, N814, N815).
650 691
651 @param node AST node to check 692 @param node AST node to check
652 @type ast.Import 693 @type ast.Import
653 @param _parents list of parent nodes (unused) 694 @param _parents list of parent nodes (unused)
654 @type list of ast.AST 695 @type list of ast.AST
655 @yield tuple giving line number, offset within line and error code
656 @ytype tuple of (int, int, str)
657 """ 696 """
658 for name in node.names: 697 for name in node.names:
659 asname = name.asname 698 asname = name.asname
660 if not asname: 699 if not asname:
661 continue 700 continue
662 701
663 originalName = name.name 702 originalName = name.name
664 if originalName.isupper(): 703 if originalName.isupper():
665 if not asname.isupper(): 704 if not asname.isupper():
666 yield self.__error(node, "N811") 705 self.__error(node, "N811")
667 elif originalName.islower(): 706 elif originalName.islower():
668 if asname.lower() != asname: 707 if asname.lower() != asname:
669 yield self.__error(node, "N812") 708 self.__error(node, "N812")
670 elif asname.islower(): 709 elif asname.islower():
671 yield self.__error(node, "N813") 710 self.__error(node, "N813")
672 elif asname.isupper(): 711 elif asname.isupper():
673 if "".join(filter(str.isupper, originalName)) == asname: 712 if "".join(filter(str.isupper, originalName)) == asname:
674 yield self.__error(node, "N815") 713 self.__error(node, "N815")
675 else: 714 else:
676 yield self.__error(node, "N814") 715 self.__error(node, "N814")

eric ide

mercurial