src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Imports/ImportsChecker.py

branch
eric7
changeset 11150
73d80859079c
parent 11147
dee6e106b4d3
equal deleted inserted replaced
11149:fc45672fae42 11150:73d80859079c
9 9
10 import ast 10 import ast
11 import copy 11 import copy
12 import re 12 import re
13 13
14 14 from CodeStyleTopicChecker import CodeStyleTopicChecker
15 class ImportsChecker: 15
16
17 class ImportsChecker(CodeStyleTopicChecker):
16 """ 18 """
17 Class implementing a checker for import statements. 19 Class implementing a checker for import statements.
18 """ 20 """
19 21
20 Codes = [ 22 Codes = [
26 "I-901", 28 "I-901",
27 "I-902", 29 "I-902",
28 "I-903", 30 "I-903",
29 "I-904", 31 "I-904",
30 ] 32 ]
33 Category = "I"
31 34
32 def __init__(self, source, filename, tree, select, ignore, expected, repeat, args): 35 def __init__(self, source, filename, tree, select, ignore, expected, repeat, args):
33 """ 36 """
34 Constructor 37 Constructor
35 38
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
345 else: 272 else:
346 minNodeLevel = 0 273 minNodeLevel = 0
347 msgCode = "I-904" 274 msgCode = "I-904"
348 275
349 if isinstance(node, ast.ImportFrom) and node.level > minNodeLevel: 276 if isinstance(node, ast.ImportFrom) and node.level > minNodeLevel:
350 self.__error(node.lineno - 1, node.col_offset, msgCode) 277 self.addErrorFromNode(node, msgCode)

eric ide

mercurial