|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the checker for simplifying Python code. |
|
8 """ |
|
9 |
|
10 import ast |
|
11 import copy |
|
12 |
|
13 from .SimplifyNodeVisitor import SimplifyNodeVisitor |
|
14 |
|
15 |
|
16 class SimplifyChecker: |
|
17 """ |
|
18 Class implementing a checker for to help simplifying Python code. |
|
19 """ |
|
20 Codes = [ |
|
21 # Python-specifics |
|
22 "Y101", "Y102", "Y103", "Y104", "Y105", "Y106", "Y107", "Y108", |
|
23 "Y109", "Y110", "Y111", "Y112", "Y113", "Y114", "Y115", "Y116", |
|
24 "Y117", "Y118", "Y119", "Y120", "Y121", "Y122", |
|
25 |
|
26 # Python-specifics not part of flake8-simplify |
|
27 "Y181", "Y182", |
|
28 |
|
29 # Comparations |
|
30 "Y201", "Y202", "Y203", "Y204", "Y205", "Y206", "Y207", "Y208", |
|
31 "Y211", "Y212", "Y213", |
|
32 "Y221", "Y222", "Y223", "Y224", |
|
33 |
|
34 # Opinionated |
|
35 "Y301", |
|
36 |
|
37 # General Code Style |
|
38 "Y401", "Y402", |
|
39 ] |
|
40 |
|
41 def __init__(self, source, filename, tree, selected, ignored, expected, |
|
42 repeat): |
|
43 """ |
|
44 Constructor |
|
45 |
|
46 @param source source code to be checked |
|
47 @type list of str |
|
48 @param filename name of the source file |
|
49 @type str |
|
50 @param tree AST tree of the source code |
|
51 @type ast.Module |
|
52 @param selected list of selected codes |
|
53 @type list of str |
|
54 @param ignored list of codes to be ignored |
|
55 @type list of str |
|
56 @param expected list of expected codes |
|
57 @type list of str |
|
58 @param repeat flag indicating to report each occurrence of a code |
|
59 @type bool |
|
60 """ |
|
61 self.__select = tuple(selected) |
|
62 self.__ignore = ('',) if selected else tuple(ignored) |
|
63 self.__expected = expected[:] |
|
64 self.__repeat = repeat |
|
65 self.__filename = filename |
|
66 self.__source = source[:] |
|
67 self.__tree = copy.deepcopy(tree) |
|
68 |
|
69 # statistics counters |
|
70 self.counters = {} |
|
71 |
|
72 # collection of detected errors |
|
73 self.errors = [] |
|
74 |
|
75 self.__checkCodes = (code for code in self.Codes |
|
76 if not self.__ignoreCode(code)) |
|
77 |
|
78 def __ignoreCode(self, code): |
|
79 """ |
|
80 Private method to check if the message code should be ignored. |
|
81 |
|
82 @param code message code to check for |
|
83 @type str |
|
84 @return flag indicating to ignore the given code |
|
85 @rtype bool |
|
86 """ |
|
87 return (code.startswith(self.__ignore) and |
|
88 not code.startswith(self.__select)) |
|
89 |
|
90 def __error(self, lineNumber, offset, code, *args): |
|
91 """ |
|
92 Private method to record an issue. |
|
93 |
|
94 @param lineNumber line number of the issue |
|
95 @type int |
|
96 @param offset position within line of the issue |
|
97 @type int |
|
98 @param code message code |
|
99 @type str |
|
100 @param args arguments for the message |
|
101 @type list |
|
102 """ |
|
103 if self.__ignoreCode(code): |
|
104 return |
|
105 |
|
106 # record the issue with one based line number |
|
107 errorInfo = { |
|
108 "file": self.__filename, |
|
109 "line": lineNumber + 1, |
|
110 "offset": offset, |
|
111 "code": code, |
|
112 "args": args, |
|
113 } |
|
114 |
|
115 if errorInfo not in self.errors: |
|
116 # this issue was not seen before |
|
117 if code in self.counters: |
|
118 self.counters[code] += 1 |
|
119 else: |
|
120 self.counters[code] = 1 |
|
121 |
|
122 # Don't care about expected codes |
|
123 if code in self.__expected: |
|
124 return |
|
125 |
|
126 if code and (self.counters[code] == 1 or self.__repeat): |
|
127 self.errors.append(errorInfo) |
|
128 |
|
129 def run(self): |
|
130 """ |
|
131 Public method to check the given source against functions |
|
132 to be replaced by 'pathlib' equivalents. |
|
133 """ |
|
134 if not self.__filename: |
|
135 # don't do anything, if essential data is missing |
|
136 return |
|
137 |
|
138 if not self.__checkCodes: |
|
139 # don't do anything, if no codes were selected |
|
140 return |
|
141 |
|
142 # Add parent information |
|
143 for node in ast.walk(self.__tree): |
|
144 for child in ast.iter_child_nodes(node): |
|
145 child.parent = node # type: ignore |
|
146 |
|
147 visitor = SimplifyNodeVisitor(self.__error) |
|
148 visitor.visit(self.__tree) |