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 = [ |
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") |