src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/PathLib/PathlibChecker.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
16 class PathlibChecker: 16 class PathlibChecker:
17 """ 17 """
18 Class implementing a checker for functions that can be replaced by use of 18 Class implementing a checker for functions that can be replaced by use of
19 the pathlib module. 19 the pathlib module.
20 """ 20 """
21
21 Codes = [ 22 Codes = [
22 ## Replacements for the os module functions 23 ## Replacements for the os module functions
23 "P101", "P102", "P103", "P104", "P105", "P106", "P107", 24 "P101",
24 "P108", "P109", "P110", "P111", "P112", "P113", "P114", 25 "P102",
25 26 "P103",
27 "P104",
28 "P105",
29 "P106",
30 "P107",
31 "P108",
32 "P109",
33 "P110",
34 "P111",
35 "P112",
36 "P113",
37 "P114",
26 ## Replacements for the os.path module functions 38 ## Replacements for the os.path module functions
27 "P201", "P202", "P203", "P204", "P205", "P206", "P207", 39 "P201",
28 "P208", "P209", "P210", "P211", "P212", "P213", 40 "P202",
29 41 "P203",
42 "P204",
43 "P205",
44 "P206",
45 "P207",
46 "P208",
47 "P209",
48 "P210",
49 "P211",
50 "P212",
51 "P213",
30 ## Replacements for some Python standrd library functions 52 ## Replacements for some Python standrd library functions
31 "P301", 53 "P301",
32
33 ## Replacements for py.path.local 54 ## Replacements for py.path.local
34 "P401", 55 "P401",
35 ] 56 ]
36 57
37 # map functions to be replaced to error codes 58 # map functions to be replaced to error codes
38 Function2Code = { 59 Function2Code = {
39 "os.chmod": "P101", 60 "os.chmod": "P101",
40 "os.mkdir": "P102", 61 "os.mkdir": "P102",
41 "os.makedirs": "P103", 62 "os.makedirs": "P103",
48 "os.readlink": "P110", 69 "os.readlink": "P110",
49 "os.stat": "P111", 70 "os.stat": "P111",
50 "os.listdir": "P112", 71 "os.listdir": "P112",
51 "os.link": "P113", 72 "os.link": "P113",
52 "os.symlink": "P114", 73 "os.symlink": "P114",
53
54 "os.path.abspath": "P201", 74 "os.path.abspath": "P201",
55 "os.path.exists": "P202", 75 "os.path.exists": "P202",
56 "os.path.expanduser": "P203", 76 "os.path.expanduser": "P203",
57 "os.path.isdir": "P204", 77 "os.path.isdir": "P204",
58 "os.path.isfile": "P205", 78 "os.path.isfile": "P205",
62 "os.path.basename": "P209", 82 "os.path.basename": "P209",
63 "os.path.dirname": "P210", 83 "os.path.dirname": "P210",
64 "os.path.samefile": "P211", 84 "os.path.samefile": "P211",
65 "os.path.splitext": "P212", 85 "os.path.splitext": "P212",
66 "os.path.relpath": "P213", 86 "os.path.relpath": "P213",
67
68 "open": "P301", 87 "open": "P301",
69
70 "py.path.local": "P401", 88 "py.path.local": "P401",
71 } 89 }
72 90
73 def __init__(self, source, filename, tree, selected, ignored, expected, 91 def __init__(self, source, filename, tree, selected, ignored, expected, repeat):
74 repeat):
75 """ 92 """
76 Constructor 93 Constructor
77 94
78 @param source source code to be checked 95 @param source source code to be checked
79 @type list of str 96 @type list of str
80 @param filename name of the source file 97 @param filename name of the source file
81 @type str 98 @type str
82 @param tree AST tree of the source code 99 @param tree AST tree of the source code
89 @type list of str 106 @type list of str
90 @param repeat flag indicating to report each occurrence of a code 107 @param repeat flag indicating to report each occurrence of a code
91 @type bool 108 @type bool
92 """ 109 """
93 self.__select = tuple(selected) 110 self.__select = tuple(selected)
94 self.__ignore = ('',) if selected else tuple(ignored) 111 self.__ignore = ("",) if selected else tuple(ignored)
95 self.__expected = expected[:] 112 self.__expected = expected[:]
96 self.__repeat = repeat 113 self.__repeat = repeat
97 self.__filename = filename 114 self.__filename = filename
98 self.__source = source[:] 115 self.__source = source[:]
99 self.__tree = copy.deepcopy(tree) 116 self.__tree = copy.deepcopy(tree)
100 117
101 # statistics counters 118 # statistics counters
102 self.counters = {} 119 self.counters = {}
103 120
104 # collection of detected errors 121 # collection of detected errors
105 self.errors = [] 122 self.errors = []
106 123
107 self.__checkCodes = (code for code in self.Codes 124 self.__checkCodes = (code for code in self.Codes if not self.__ignoreCode(code))
108 if not self.__ignoreCode(code)) 125
109
110 def __ignoreCode(self, code): 126 def __ignoreCode(self, code):
111 """ 127 """
112 Private method to check if the message code should be ignored. 128 Private method to check if the message code should be ignored.
113 129
114 @param code message code to check for 130 @param code message code to check for
115 @type str 131 @type str
116 @return flag indicating to ignore the given code 132 @return flag indicating to ignore the given code
117 @rtype bool 133 @rtype bool
118 """ 134 """
119 return (code.startswith(self.__ignore) and 135 return code.startswith(self.__ignore) and not code.startswith(self.__select)
120 not code.startswith(self.__select)) 136
121
122 def __error(self, lineNumber, offset, code, *args): 137 def __error(self, lineNumber, offset, code, *args):
123 """ 138 """
124 Private method to record an issue. 139 Private method to record an issue.
125 140
126 @param lineNumber line number of the issue 141 @param lineNumber line number of the issue
127 @type int 142 @type int
128 @param offset position within line of the issue 143 @param offset position within line of the issue
129 @type int 144 @type int
130 @param code message code 145 @param code message code
132 @param args arguments for the message 147 @param args arguments for the message
133 @type list 148 @type list
134 """ 149 """
135 if self.__ignoreCode(code): 150 if self.__ignoreCode(code):
136 return 151 return
137 152
138 if code in self.counters: 153 if code in self.counters:
139 self.counters[code] += 1 154 self.counters[code] += 1
140 else: 155 else:
141 self.counters[code] = 1 156 self.counters[code] = 1
142 157
143 # Don't care about expected codes 158 # Don't care about expected codes
144 if code in self.__expected: 159 if code in self.__expected:
145 return 160 return
146 161
147 if code and (self.counters[code] == 1 or self.__repeat): 162 if code and (self.counters[code] == 1 or self.__repeat):
148 # record the issue with one based line number 163 # record the issue with one based line number
149 self.errors.append( 164 self.errors.append(
150 { 165 {
151 "file": self.__filename, 166 "file": self.__filename,
153 "offset": offset, 168 "offset": offset,
154 "code": code, 169 "code": code,
155 "args": args, 170 "args": args,
156 } 171 }
157 ) 172 )
158 173
159 def run(self): 174 def run(self):
160 """ 175 """
161 Public method to check the given source against functions 176 Public method to check the given source against functions
162 to be replaced by 'pathlib' equivalents. 177 to be replaced by 'pathlib' equivalents.
163 """ 178 """
164 if not self.__filename: 179 if not self.__filename:
165 # don't do anything, if essential data is missing 180 # don't do anything, if essential data is missing
166 return 181 return
167 182
168 if not self.__checkCodes: 183 if not self.__checkCodes:
169 # don't do anything, if no codes were selected 184 # don't do anything, if no codes were selected
170 return 185 return
171 186
172 visitor = PathlibVisitor(self.__checkForReplacement) 187 visitor = PathlibVisitor(self.__checkForReplacement)
173 visitor.visit(self.__tree) 188 visitor.visit(self.__tree)
174 189
175 def __checkForReplacement(self, node, name): 190 def __checkForReplacement(self, node, name):
176 """ 191 """
177 Private method to check the given node for the need for a 192 Private method to check the given node for the need for a
178 replacement. 193 replacement.
179 194
180 @param node reference to the AST node to check 195 @param node reference to the AST node to check
181 @type ast.AST 196 @type ast.AST
182 @param name resolved name of the node 197 @param name resolved name of the node
183 @type str 198 @type str
184 """ 199 """
189 204
190 class PathlibVisitor(ast.NodeVisitor): 205 class PathlibVisitor(ast.NodeVisitor):
191 """ 206 """
192 Class to traverse the AST node tree and check for potential issues. 207 Class to traverse the AST node tree and check for potential issues.
193 """ 208 """
209
194 def __init__(self, checkCallback): 210 def __init__(self, checkCallback):
195 """ 211 """
196 Constructor 212 Constructor
197 213
198 @param checkCallback callback function taking a reference to the 214 @param checkCallback callback function taking a reference to the
199 AST node and the resolved name 215 AST node and the resolved name
200 @type func 216 @type func
201 """ 217 """
202 super().__init__() 218 super().__init__()
203 219
204 self.__checkCallback = checkCallback 220 self.__checkCallback = checkCallback
205 self.__importAlias = {} 221 self.__importAlias = {}
206 222
207 def visit_ImportFrom(self, node): 223 def visit_ImportFrom(self, node):
208 """ 224 """
209 Public method handle the ImportFrom AST node. 225 Public method handle the ImportFrom AST node.
210 226
211 @param node reference to the ImportFrom AST node 227 @param node reference to the ImportFrom AST node
212 @type ast.ImportFrom 228 @type ast.ImportFrom
213 """ 229 """
214 for imp in node.names: 230 for imp in node.names:
215 if imp.asname: 231 if imp.asname:
218 self.__importAlias[imp.name] = f"{node.module}.{imp.name}" 234 self.__importAlias[imp.name] = f"{node.module}.{imp.name}"
219 235
220 def visit_Import(self, node): 236 def visit_Import(self, node):
221 """ 237 """
222 Public method to handle the Import AST node. 238 Public method to handle the Import AST node.
223 239
224 @param node reference to the Import AST node 240 @param node reference to the Import AST node
225 @type ast.Import 241 @type ast.Import
226 """ 242 """
227 for imp in node.names: 243 for imp in node.names:
228 if imp.asname: 244 if imp.asname:
229 self.__importAlias[imp.asname] = imp.name 245 self.__importAlias[imp.asname] = imp.name
230 246
231 def visit_Call(self, node): 247 def visit_Call(self, node):
232 """ 248 """
233 Public method to handle the Call AST node. 249 Public method to handle the Call AST node.
234 250
235 @param node reference to the Call AST node 251 @param node reference to the Call AST node
236 @type ast.Call 252 @type ast.Call
237 """ 253 """
238 nameResolver = NameResolver(self.__importAlias) 254 nameResolver = NameResolver(self.__importAlias)
239 nameResolver.visit(node.func) 255 nameResolver.visit(node.func)
240 256
241 self.__checkCallback(node, nameResolver.name()) 257 self.__checkCallback(node, nameResolver.name())
242 258
243 259
244 class NameResolver(ast.NodeVisitor): 260 class NameResolver(ast.NodeVisitor):
245 """ 261 """
246 Class to resolve a Name or Attribute node. 262 Class to resolve a Name or Attribute node.
247 """ 263 """
264
248 def __init__(self, importAlias): 265 def __init__(self, importAlias):
249 """ 266 """
250 Constructor 267 Constructor
251 268
252 @param importAlias reference to the import aliases dictionary 269 @param importAlias reference to the import aliases dictionary
253 @type dict 270 @type dict
254 """ 271 """
255 self.__importAlias = importAlias 272 self.__importAlias = importAlias
256 self.__names = [] 273 self.__names = []
257 274
258 def name(self): 275 def name(self):
259 """ 276 """
260 Public method to resolve the name. 277 Public method to resolve the name.
261 278
262 @return resolved name 279 @return resolved name
263 @rtype str 280 @rtype str
264 """ 281 """
265 with contextlib.suppress(KeyError, IndexError): 282 with contextlib.suppress(KeyError, IndexError):
266 attr = self.__importAlias[self.__names[-1]] 283 attr = self.__importAlias[self.__names[-1]]
267 self.__names[-1] = attr 284 self.__names[-1] = attr
268 # do nothing if there is no such name or the names list is empty 285 # do nothing if there is no such name or the names list is empty
269 286
270 return ".".join(reversed(self.__names)) 287 return ".".join(reversed(self.__names))
271 288
272 def visit_Name(self, node): 289 def visit_Name(self, node):
273 """ 290 """
274 Public method to handle the Name AST node. 291 Public method to handle the Name AST node.
275 292
276 @param node reference to the Name AST node 293 @param node reference to the Name AST node
277 @type ast.Name 294 @type ast.Name
278 """ 295 """
279 self.__names.append(node.id) 296 self.__names.append(node.id)
280 297
281 def visit_Attribute(self, node): 298 def visit_Attribute(self, node):
282 """ 299 """
283 Public method to handle the Attribute AST node. 300 Public method to handle the Attribute AST node.
284 301
285 @param node reference to the Attribute AST node 302 @param node reference to the Attribute AST node
286 @type ast.Attribute 303 @type ast.Attribute
287 """ 304 """
288 try: 305 try:
289 self.__names.append(node.attr) 306 self.__names.append(node.attr)

eric ide

mercurial