48 @param repeat flag indicating to report each occurrence of a code |
51 @param repeat flag indicating to report each occurrence of a code |
49 @type bool |
52 @type bool |
50 @param args dictionary of arguments for the various checks |
53 @param args dictionary of arguments for the various checks |
51 @type dict |
54 @type dict |
52 """ |
55 """ |
53 self.__select = tuple(select) |
56 super().__init__( |
54 self.__ignore = tuple(ignore) |
57 ImportsChecker.Category, |
55 self.__expected = expected[:] |
58 source, |
56 self.__repeat = repeat |
59 filename, |
57 self.__filename = filename |
60 tree, |
58 self.__source = source[:] |
61 select, |
59 self.__tree = copy.deepcopy(tree) |
62 ignore, |
60 self.__args = args |
63 expected, |
61 |
64 repeat, |
62 # statistics counters |
65 args, |
63 self.counters = {} |
66 ) |
64 |
|
65 # collection of detected errors |
|
66 self.errors = [] |
|
67 |
67 |
68 checkersWithCodes = [ |
68 checkersWithCodes = [ |
69 (self.__checkLocalImports, ("I-101", "I-102", "I-103")), |
69 (self.__checkLocalImports, ("I-101", "I-102", "I-103")), |
70 (self.__tidyImports, ("I-901", "I-902", "I-903", "I-904")), |
70 (self.__tidyImports, ("I-901", "I-902", "I-903", "I-904")), |
71 ] |
71 ] |
72 |
72 self._initializeCheckers(checkersWithCodes) |
73 self.__checkers = [] |
|
74 for checker, codes in checkersWithCodes: |
|
75 if any(not (code and self.__ignoreCode(code)) for code in codes): |
|
76 self.__checkers.append(checker) |
|
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 in self.__ignore or ( |
|
88 code.startswith(self.__ignore) and not code.startswith(self.__select) |
|
89 ) |
|
90 |
|
91 def __error(self, lineNumber, offset, code, *args): |
|
92 """ |
|
93 Private method to record an issue. |
|
94 |
|
95 @param lineNumber line number of the issue |
|
96 @type int |
|
97 @param offset position within line of the issue |
|
98 @type int |
|
99 @param code message code |
|
100 @type str |
|
101 @param args arguments for the message |
|
102 @type list |
|
103 """ |
|
104 if self.__ignoreCode(code): |
|
105 return |
|
106 |
|
107 if code in self.counters: |
|
108 self.counters[code] += 1 |
|
109 else: |
|
110 self.counters[code] = 1 |
|
111 |
|
112 # Don't care about expected codes |
|
113 if code in self.__expected: |
|
114 return |
|
115 |
|
116 if code and (self.counters[code] == 1 or self.__repeat): |
|
117 # record the issue with one based line number |
|
118 self.errors.append( |
|
119 { |
|
120 "file": self.__filename, |
|
121 "line": lineNumber + 1, |
|
122 "offset": offset, |
|
123 "code": code, |
|
124 "args": args, |
|
125 } |
|
126 ) |
|
127 |
|
128 def run(self): |
|
129 """ |
|
130 Public method to check the given source against miscellaneous |
|
131 conditions. |
|
132 """ |
|
133 if not self.__filename: |
|
134 # don't do anything, if essential data is missing |
|
135 return |
|
136 |
|
137 if not self.__checkers: |
|
138 # don't do anything, if no codes were selected |
|
139 return |
|
140 |
|
141 for check in self.__checkers: |
|
142 check() |
|
143 |
73 |
144 ####################################################################### |
74 ####################################################################### |
145 ## Local imports |
75 ## Local imports |
146 ## |
76 ## |
147 ## adapted from: flake8-local-import v1.0.6 |
77 ## adapted from: flake8-local-import v1.0.6 |
151 """ |
81 """ |
152 Private method to check local imports. |
82 Private method to check local imports. |
153 """ |
83 """ |
154 from .LocalImportVisitor import LocalImportVisitor |
84 from .LocalImportVisitor import LocalImportVisitor |
155 |
85 |
156 visitor = LocalImportVisitor(self.__args, self) |
86 visitor = LocalImportVisitor(self.args, self) |
157 visitor.visit(copy.deepcopy(self.__tree)) |
87 visitor.visit(copy.deepcopy(self.tree)) |
158 for violation in visitor.violations: |
88 for violation in visitor.violations: |
159 if not self.__ignoreCode(violation[1]): |
89 self.addErrorFromNode(violation[0], violation[1]) |
160 node = violation[0] |
|
161 reason = violation[1] |
|
162 self.__error(node.lineno - 1, node.col_offset, reason) |
|
163 |
90 |
164 ####################################################################### |
91 ####################################################################### |
165 ## Tidy imports |
92 ## Tidy imports |
166 ## |
93 ## |
167 ## adapted from: flake8-tidy-imports v4.11.0 |
94 ## adapted from: flake8-tidy-imports v4.11.0 |
169 |
96 |
170 def __tidyImports(self): |
97 def __tidyImports(self): |
171 """ |
98 """ |
172 Private method to check various other import related topics. |
99 Private method to check various other import related topics. |
173 """ |
100 """ |
174 self.__banRelativeImports = self.__args.get("BanRelativeImports", "") |
101 self.__banRelativeImports = self.args.get("BanRelativeImports", "") |
175 self.__bannedModules = [] |
102 self.__bannedModules = [] |
176 self.__bannedStructuredPatterns = [] |
103 self.__bannedStructuredPatterns = [] |
177 self.__bannedUnstructuredPatterns = [] |
104 self.__bannedUnstructuredPatterns = [] |
178 for module in self.__args.get("BannedModules", []): |
105 for module in self.args.get("BannedModules", []): |
179 module = module.strip() |
106 module = module.strip() |
180 if "*" in module[:-1] or module == "*": |
107 if "*" in module[:-1] or module == "*": |
181 # unstructured |
108 # unstructured |
182 self.__bannedUnstructuredPatterns.append( |
109 self.__bannedUnstructuredPatterns.append( |
183 self.__compileUnstructuredGlob(module) |
110 self.__compileUnstructuredGlob(module) |
195 |
122 |
196 # Sort the structured patterns so we match the specifc ones first. |
123 # Sort the structured patterns so we match the specifc ones first. |
197 self.__bannedStructuredPatterns.sort(key=lambda x: len(x[0]), reverse=True) |
124 self.__bannedStructuredPatterns.sort(key=lambda x: len(x[0]), reverse=True) |
198 |
125 |
199 ruleMethods = [] |
126 ruleMethods = [] |
200 if not self.__ignoreCode("I-901"): |
127 if not self._ignoreCode("I-901"): |
201 ruleMethods.append(self.__checkUnnecessaryAlias) |
128 ruleMethods.append(self.__checkUnnecessaryAlias) |
202 if not self.__ignoreCode("I-902") and bool(self.__bannedModules): |
129 if not self._ignoreCode("I-902") and bool(self.__bannedModules): |
203 ruleMethods.append(self.__checkBannedImport) |
130 ruleMethods.append(self.__checkBannedImport) |
204 if ( |
131 if ( |
205 not self.__ignoreCode("I-903") and self.__banRelativeImports == "parents" |
132 not self._ignoreCode("I-903") and self.__banRelativeImports == "parents" |
206 ) or (not self.__ignoreCode("I-904") and self.__banRelativeImports == "true"): |
133 ) or (not self._ignoreCode("I-904") and self.__banRelativeImports == "true"): |
207 ruleMethods.append(self.__checkBannedRelativeImports) |
134 ruleMethods.append(self.__checkBannedRelativeImports) |
208 |
135 |
209 for node in ast.walk(self.__tree): |
136 for node in ast.walk(self.tree): |
210 for method in ruleMethods: |
137 for method in ruleMethods: |
211 method(node) |
138 method(node) |
212 |
139 |
213 def __compileUnstructuredGlob(self, module): |
140 def __compileUnstructuredGlob(self, module): |
214 """ |
141 """ |
249 if fromName: |
176 if fromName: |
250 rewritten = f"from {fromName} import {importedName}" |
177 rewritten = f"from {fromName} import {importedName}" |
251 else: |
178 else: |
252 rewritten = f"import {importedName}" |
179 rewritten = f"import {importedName}" |
253 |
180 |
254 self.__error(node.lineno - 1, node.col_offset, "I-901", rewritten) |
181 self.addErrorFromNode(node, "I-901", rewritten) |
255 |
182 |
256 elif isinstance(node, ast.ImportFrom): |
183 elif isinstance(node, ast.ImportFrom): |
257 for alias in node.names: |
184 for alias in node.names: |
258 if alias.name == alias.asname: |
185 if alias.name == alias.asname: |
259 rewritten = f"from {node.module} import {alias.name}" |
186 rewritten = f"from {node.module} import {alias.name}" |
260 |
187 |
261 self.__error(node.lineno - 1, node.col_offset, "I-901", rewritten) |
188 self.addErrorFromNode(node, "I-901", rewritten) |
262 |
189 |
263 def __isModuleBanned(self, moduleName): |
190 def __isModuleBanned(self, moduleName): |
264 """ |
191 """ |
265 Private method to check, if the given module name banned. |
192 Private method to check, if the given module name banned. |
266 |
193 |
324 # Do not show an error for this line if we already showed |
251 # Do not show an error for this line if we already showed |
325 # a more specific error. |
252 # a more specific error. |
326 continue |
253 continue |
327 else: |
254 else: |
328 warned.add(moduleName) |
255 warned.add(moduleName) |
329 self.__error(node.lineno - 1, node.col_offset, "I-902", moduleName) |
256 self.addErrorFromNode(node, "I-902", moduleName) |
330 |
257 |
331 def __checkBannedRelativeImports(self, node): |
258 def __checkBannedRelativeImports(self, node): |
332 """ |
259 """ |
333 Private method to check if relative imports are banned. |
260 Private method to check if relative imports are banned. |
334 |
261 |