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

branch
eric7
changeset 11150
73d80859079c
parent 11148
15e30f0c76a8
child 11163
f6c0a18254d6
equal deleted inserted replaced
11149:fc45672fae42 11150:73d80859079c
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
13 import functools 12 import functools
14 import os 13 import os
15 14
16 try: 15 try:
17 ast.AsyncFunctionDef # __IGNORE_EXCEPTION__ 16 ast.AsyncFunctionDef # __IGNORE_EXCEPTION__
18 except AttributeError: 17 except AttributeError:
19 ast.AsyncFunctionDef = ast.FunctionDef 18 ast.AsyncFunctionDef = ast.FunctionDef
20 19
21 20 from CodeStyleTopicChecker import CodeStyleTopicChecker
22 # TODO: change this to a checker like the others 21
23 class NamingStyleChecker: 22
23 class NamingStyleChecker(CodeStyleTopicChecker):
24 """ 24 """
25 Class implementing a checker for naming conventions. 25 Class implementing a checker for naming conventions.
26 """ 26 """
27 27
28 Codes = [ 28 Codes = [
44 "N-821", 44 "N-821",
45 "N-822", 45 "N-822",
46 "N-823", 46 "N-823",
47 "N-831", 47 "N-831",
48 ] 48 ]
49 Category = "N"
49 50
50 def __init__(self, source, filename, tree, select, ignore, expected, repeat, args): 51 def __init__(self, source, filename, tree, select, ignore, expected, repeat, args):
51 """ 52 """
52 Constructor 53 Constructor
53 54
66 @param repeat flag indicating to report each occurrence of a code 67 @param repeat flag indicating to report each occurrence of a code
67 @type bool 68 @type bool
68 @param args dictionary of arguments for the various checks 69 @param args dictionary of arguments for the various checks
69 @type dict 70 @type dict
70 """ 71 """
71 self.__select = tuple(select) 72 super().__init__(
72 self.__ignore = tuple(ignore) 73 NamingStyleChecker.Category,
73 self.__expected = expected[:] 74 source,
74 self.__repeat = repeat 75 filename,
75 self.__filename = filename 76 tree,
76 self.__source = source[:] 77 select,
77 self.__tree = copy.deepcopy(tree) 78 ignore,
78 self.__args = args 79 expected,
80 repeat,
81 args,
82 )
79 83
80 self.__parents = collections.deque() 84 self.__parents = collections.deque()
81
82 # statistics counters
83 self.counters = {}
84
85 # collection of detected errors
86 self.errors = []
87 85
88 self.__checkersWithCodes = { 86 self.__checkersWithCodes = {
89 "classdef": [ 87 "classdef": [
90 (self.__checkClassName, ("N-801", "N-818")), 88 (self.__checkClassName, ("N-801", "N-818")),
91 (self.__checkNameToBeAvoided, ("N-831",)), 89 (self.__checkNameToBeAvoided, ("N-831",)),
128 ] 126 ]
129 127
130 self.__checkers = collections.defaultdict(list) 128 self.__checkers = collections.defaultdict(list)
131 for key, checkers in self.__checkersWithCodes.items(): 129 for key, checkers in self.__checkersWithCodes.items():
132 for checker, codes in checkers: 130 for checker, codes in checkers:
133 if any(not (code and self.__ignoreCode(code)) for code in codes): 131 if any(not (code and self._ignoreCode(code)) for code in codes):
134 self.__checkers[key].append(checker) 132 self.__checkers[key].append(checker)
135 133
136 def __ignoreCode(self, code): 134 def addErrorFromNode(self, node, msgCode):
137 """ 135 """
138 Private method to check if the message code should be ignored. 136 Public method to build the error information.
139
140 @param code message code to check for
141 @type str
142 @return flag indicating to ignore the given code
143 @rtype bool
144 """
145 return code in self.__ignore or (
146 code.startswith(self.__ignore) and not code.startswith(self.__select)
147 )
148
149 def __error(self, node, code):
150 """
151 Private method to build the error information.
152 137
153 @param node AST node to report an error for 138 @param node AST node to report an error for
154 @type ast.AST 139 @type ast.AST
155 @param code error code to report 140 @param msgCode message code
156 @type str 141 @type str
157 """ 142 """
158 if self.__ignoreCode(code): 143 if self._ignoreCode(msgCode):
159 return 144 return
160 145
161 if isinstance(node, ast.Module): 146 if isinstance(node, ast.Module):
162 lineno = 0 147 lineno = 0
163 offset = 0 148 offset = 0
169 offset += 6 154 offset += 6
170 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): 155 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
171 lineno += len(node.decorator_list) 156 lineno += len(node.decorator_list)
172 offset += 4 157 offset += 4
173 158
174 # record the issue with one based line number 159 self.addError(lineno, offset, msgCode, [])
175 errorInfo = {
176 "file": self.__filename,
177 "line": lineno,
178 "offset": offset,
179 "code": code,
180 "args": [],
181 }
182
183 if errorInfo not in self.errors:
184 # this issue was not seen before
185 if code in self.counters:
186 self.counters[code] += 1
187 else:
188 self.counters[code] = 1
189
190 # Don't care about expected codes
191 if code in self.__expected:
192 return
193
194 if code and (self.counters[code] == 1 or self.__repeat):
195 self.errors.append(errorInfo)
196 160
197 def run(self): 161 def run(self):
198 """ 162 """
199 Public method run by the pycodestyle.py checker. 163 Public method to execute the relevant checks.
200 164 """
201 @return tuple giving line number, offset within line, code and 165 if not self.filename:
202 checker function 166 # don't do anything, if essential data is missing
203 @rtype tuple of (int, int, str, function) 167 return
204 """ 168
205 if self.__tree and self.__checkers: 169 if not self.__checkers:
206 return self.__visitTree(self.__tree) 170 # don't do anything, if no codes were selected
207 else: 171 return
208 return () 172
173 self.__visitTree(self.tree)
209 174
210 def __visitTree(self, node): 175 def __visitTree(self, node):
211 """ 176 """
212 Private method to scan the given AST tree. 177 Private method to scan the given AST tree.
213 178
336 @type list of ast.AST 301 @type list of ast.AST
337 """ 302 """
338 if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): 303 if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
339 name = node.name 304 name = node.name
340 if self.__isNameToBeAvoided(name): 305 if self.__isNameToBeAvoided(name):
341 self.__error(node, "N-831") 306 self.addErrorFromNode(node, "N-831")
342 307
343 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): 308 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
344 argNames = self.__getArgNames(node) 309 argNames = self.__getArgNames(node)
345 for arg in argNames: 310 for arg in argNames:
346 if self.__isNameToBeAvoided(arg): 311 if self.__isNameToBeAvoided(arg):
347 self.__error(node, "N-831") 312 self.addErrorFromNode(node, "N-831")
348 313
349 elif isinstance(node, (ast.Assign, ast.NamedExpr, ast.AnnAssign)): 314 elif isinstance(node, (ast.Assign, ast.NamedExpr, ast.AnnAssign)):
350 if isinstance(node, ast.Assign): 315 if isinstance(node, ast.Assign):
351 targets = node.targets 316 targets = node.targets
352 else: 317 else:
353 targets = [node.target] 318 targets = [node.target]
354 for target in targets: 319 for target in targets:
355 if isinstance(target, ast.Name): 320 if isinstance(target, ast.Name):
356 name = target.id 321 name = target.id
357 if bool(name) and self.__isNameToBeAvoided(name): 322 if bool(name) and self.__isNameToBeAvoided(name):
358 self.__error(node, "N-831") 323 self.addErrorFromNode(node, "N-831")
359 324
360 elif isinstance(target, (ast.Tuple, ast.List)): 325 elif isinstance(target, (ast.Tuple, ast.List)):
361 for element in target.elts: 326 for element in target.elts:
362 if isinstance(element, ast.Name): 327 if isinstance(element, ast.Name):
363 name = element.id 328 name = element.id
364 if bool(name) and self.__isNameToBeAvoided(name): 329 if bool(name) and self.__isNameToBeAvoided(name):
365 self.__error(node, "N-831") 330 self.addErrorFromNode(node, "N-831")
366 331
367 def __getClassdef(self, name, parents): 332 def __getClassdef(self, name, parents):
368 """ 333 """
369 Private method to extract the class definition. 334 Private method to extract the class definition.
370 335
423 @type list of ast.AST 388 @type list of ast.AST
424 """ 389 """
425 name = node.name 390 name = node.name
426 strippedName = name.strip("_") 391 strippedName = name.strip("_")
427 if not strippedName[:1].isupper() or "_" in strippedName: 392 if not strippedName[:1].isupper() or "_" in strippedName:
428 self.__error(node, "N-801") 393 self.addErrorFromNode(node, "N-801")
429 394
430 superClasses = self.__superClassNames(name, parents) 395 superClasses = self.__superClassNames(name, parents)
431 if "Exception" in superClasses and not name.endswith("Error"): 396 if "Exception" in superClasses and not name.endswith("Error"):
432 self.__error(node, "N-818") 397 self.addErrorFromNode(node, "N-818")
433 398
434 def __checkFunctionName(self, node, _parents): 399 def __checkFunctionName(self, node, _parents):
435 """ 400 """
436 Private class to check the given node for function name 401 Private class to check the given node for function name
437 conventions (N802, N809). 402 conventions (N802, N809).
452 417
453 if name in ("__dir__", "__getattr__"): 418 if name in ("__dir__", "__getattr__"):
454 return 419 return
455 420
456 if name.lower() != name: 421 if name.lower() != name:
457 self.__error(node, "N-802") 422 self.addErrorFromNode(node, "N-802")
458 if functionType == "function" and name[:2] == "__" and name[-2:] == "__": 423 if functionType == "function" and name[:2] == "__" and name[-2:] == "__":
459 self.__error(node, "N-809") 424 self.addErrorFromNode(node, "N-809")
460 425
461 def __checkFunctionArgumentNames(self, node, _parents): 426 def __checkFunctionArgumentNames(self, node, _parents):
462 """ 427 """
463 Private class to check the argument names of functions 428 Private class to check the argument names of functions
464 (N803, N804, N805, N806). 429 (N803, N804, N805, N806).
473 @type list of ast.AST 438 @type list of ast.AST
474 """ 439 """
475 if node.args.kwarg is not None: 440 if node.args.kwarg is not None:
476 kwarg = node.args.kwarg.arg 441 kwarg = node.args.kwarg.arg
477 if kwarg.lower() != kwarg: 442 if kwarg.lower() != kwarg:
478 self.__error(node, "N-803") 443 self.addErrorFromNode(node, "N-803")
479 444
480 elif node.args.vararg is not None: 445 elif node.args.vararg is not None:
481 vararg = node.args.vararg.arg 446 vararg = node.args.vararg.arg
482 if vararg.lower() != vararg: 447 if vararg.lower() != vararg:
483 self.__error(node, "N-803") 448 self.addErrorFromNode(node, "N-803")
484 449
485 else: 450 else:
486 argNames = self.__getArgNames(node) 451 argNames = self.__getArgNames(node)
487 functionType = getattr(node, "function_type", "function") 452 functionType = getattr(node, "function_type", "function")
488 453
489 if not argNames: 454 if not argNames:
490 if functionType == "method": 455 if functionType == "method":
491 self.__error(node, "N-805") 456 self.addErrorFromNode(node, "N-805")
492 elif functionType == "classmethod": 457 elif functionType == "classmethod":
493 self.__error(node, "N-804") 458 self.addErrorFromNode(node, "N-804")
494 459
495 elif functionType == "method" and argNames[0] != "self": 460 elif functionType == "method" and argNames[0] != "self":
496 self.__error(node, "N-805") 461 self.addErrorFromNode(node, "N-805")
497 elif functionType == "classmethod" and argNames[0] != "cls": 462 elif functionType == "classmethod" and argNames[0] != "cls":
498 self.__error(node, "N-804") 463 self.addErrorFromNode(node, "N-804")
499 elif functionType == "staticmethod" and argNames[0] in ("cls", "self"): 464 elif functionType == "staticmethod" and argNames[0] in ("cls", "self"):
500 self.__error(node, "N-806") 465 self.addErrorFromNode(node, "N-806")
501 for arg in argNames: 466 for arg in argNames:
502 if arg.lower() != arg: 467 if arg.lower() != arg:
503 self.__error(node, "N-803") 468 self.addErrorFromNode(node, "N-803")
504 break 469 break
505 470
506 def __checkVariableNames(self, node, parents): 471 def __checkVariableNames(self, node, parents):
507 """ 472 """
508 Private method to check variable names in function, class and global scope 473 Private method to check variable names in function, class and global scope
561 else: 526 else:
562 checker = self.__globalVariableCheck 527 checker = self.__globalVariableCheck
563 for name in self.__extractNames(assignmentTarget): 528 for name in self.__extractNames(assignmentTarget):
564 errorCode = checker(name) 529 errorCode = checker(name)
565 if errorCode: 530 if errorCode:
566 self.__error(assignmentTarget, errorCode) 531 self.addErrorFromNode(assignmentTarget, errorCode)
567 532
568 def __extractNames(self, assignmentTarget): 533 def __extractNames(self, assignmentTarget):
569 """ 534 """
570 Private method to extract the names from the target node. 535 Private method to extract the names from the target node.
571 536
676 @type list of ast.AST 641 @type list of ast.AST
677 """ 642 """
678 if self.__filename: 643 if self.__filename:
679 moduleName = os.path.splitext(os.path.basename(self.__filename))[0] 644 moduleName = os.path.splitext(os.path.basename(self.__filename))[0]
680 if moduleName.lower() != moduleName: 645 if moduleName.lower() != moduleName:
681 self.__error(node, "N-807") 646 self.addErrorFromNode(node, "N-807")
682 647
683 if moduleName == "__init__": 648 if moduleName == "__init__":
684 # we got a package 649 # we got a package
685 packageName = os.path.split(os.path.dirname(self.__filename))[1] 650 packageName = os.path.split(os.path.dirname(self.__filename))[1]
686 if packageName.lower() != packageName: 651 if packageName.lower() != packageName:
687 self.__error(node, "N-808") 652 self.addErrorFromNode(node, "N-808")
688 653
689 def __checkImportAs(self, node, _parents): 654 def __checkImportAs(self, node, _parents):
690 """ 655 """
691 Private method to check that imports don't change the 656 Private method to check that imports don't change the
692 naming convention (N811, N812, N813, N814, N815). 657 naming convention (N811, N812, N813, N814, N815).
702 continue 667 continue
703 668
704 originalName = name.name 669 originalName = name.name
705 if originalName.isupper(): 670 if originalName.isupper():
706 if not asname.isupper(): 671 if not asname.isupper():
707 self.__error(node, "N-811") 672 self.addErrorFromNode(node, "N-811")
708 elif originalName.islower(): 673 elif originalName.islower():
709 if asname.lower() != asname: 674 if asname.lower() != asname:
710 self.__error(node, "N-812") 675 self.addErrorFromNode(node, "N-812")
711 elif asname.islower(): 676 elif asname.islower():
712 self.__error(node, "N-813") 677 self.addErrorFromNode(node, "N-813")
713 elif asname.isupper(): 678 elif asname.isupper():
714 if "".join(filter(str.isupper, originalName)) == asname: 679 if "".join(filter(str.isupper, originalName)) == asname:
715 self.__error(node, "N-815") 680 self.addErrorFromNode(node, "N-815")
716 else: 681 else:
717 self.__error(node, "N-814") 682 self.addErrorFromNode(node, "N-814")

eric ide

mercurial