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

branch
eric7
changeset 10046
35b27af462ef
parent 9653
e67609152c5e
child 10184
af82cb6e0298
equal deleted inserted replaced
10045:f5c57f8d17a4 10046:35b27af462ef
8 """ 8 """
9 9
10 import ast 10 import ast
11 import copy 11 import copy
12 import re 12 import re
13 import sys
14 13
15 14
16 class ImportsChecker: 15 class ImportsChecker:
17 """ 16 """
18 Class implementing a checker for import statements. 17 Class implementing a checker for import statements.
21 Codes = [ 20 Codes = [
22 ## Local imports 21 ## Local imports
23 "I101", 22 "I101",
24 "I102", 23 "I102",
25 "I103", 24 "I103",
26 ## Imports order
27 "I201",
28 "I202",
29 "I203",
30 "I204",
31 ## Various other import related 25 ## Various other import related
32 "I901", 26 "I901",
33 "I902", 27 "I902",
34 "I903", 28 "I903",
35 "I904", 29 "I904",
63 self.__filename = filename 57 self.__filename = filename
64 self.__source = source[:] 58 self.__source = source[:]
65 self.__tree = copy.deepcopy(tree) 59 self.__tree = copy.deepcopy(tree)
66 self.__args = args 60 self.__args = args
67 61
68 # parameters for import sorting
69 if args["SortOrder"] == "native":
70 self.__sortingFunction = sorted
71 else:
72 # naturally is the default sort order
73 self.__sortingFunction = self.__naturally
74 self.__sortCaseSensitive = args["SortCaseSensitive"]
75
76 # statistics counters 62 # statistics counters
77 self.counters = {} 63 self.counters = {}
78 64
79 # collection of detected errors 65 # collection of detected errors
80 self.errors = [] 66 self.errors = []
81 67
82 checkersWithCodes = [ 68 checkersWithCodes = [
83 (self.__checkLocalImports, ("I101", "I102", "I103")), 69 (self.__checkLocalImports, ("I101", "I102", "I103")),
84 (self.__checkImportOrder, ("I201", "I202", "I203", "I204")),
85 (self.__tidyImports, ("I901", "I902", "I903", "I904")), 70 (self.__tidyImports, ("I901", "I902", "I903", "I904")),
86 ] 71 ]
87 72
88 self.__checkers = [] 73 self.__checkers = []
89 for checker, codes in checkersWithCodes: 74 for checker, codes in checkersWithCodes:
152 return 137 return
153 138
154 for check in self.__checkers: 139 for check in self.__checkers:
155 check() 140 check()
156 141
157 def getStandardModules(self):
158 """
159 Public method to get a list of modules of the standard library.
160
161 @return set of builtin modules
162 @rtype set of str
163 """
164 try:
165 return sys.stdlib_module_names
166 except AttributeError:
167 return {
168 "__future__",
169 "__main__",
170 "_dummy_thread",
171 "_thread",
172 "abc",
173 "aifc",
174 "argparse",
175 "array",
176 "ast",
177 "asynchat",
178 "asyncio",
179 "asyncore",
180 "atexit",
181 "audioop",
182 "base64",
183 "bdb",
184 "binascii",
185 "binhex",
186 "bisect",
187 "builtins",
188 "bz2",
189 "calendar",
190 "cgi",
191 "cgitb",
192 "chunk",
193 "cmath",
194 "cmd",
195 "code",
196 "codecs",
197 "codeop",
198 "collections",
199 "colorsys",
200 "compileall",
201 "concurrent",
202 "configparser",
203 "contextlib",
204 "contextvars",
205 "copy",
206 "copyreg",
207 "cProfile",
208 "crypt",
209 "csv",
210 "ctypes",
211 "curses",
212 "dataclasses",
213 "datetime",
214 "dbm",
215 "decimal",
216 "difflib",
217 "dis",
218 "distutils",
219 "doctest",
220 "dummy_threading",
221 "email",
222 "encodings",
223 "ensurepip",
224 "enum",
225 "errno",
226 "faulthandler",
227 "fcntl",
228 "filecmp",
229 "fileinput",
230 "fnmatch",
231 "formatter",
232 "fractions",
233 "ftplib",
234 "functools",
235 "gc",
236 "getopt",
237 "getpass",
238 "gettext",
239 "glob",
240 "grp",
241 "gzip",
242 "hashlib",
243 "heapq",
244 "hmac",
245 "html",
246 "http",
247 "imaplib",
248 "imghdr",
249 "imp",
250 "importlib",
251 "inspect",
252 "io",
253 "ipaddress",
254 "itertools",
255 "json",
256 "keyword",
257 "lib2to3",
258 "linecache",
259 "locale",
260 "logging",
261 "lzma",
262 "mailbox",
263 "mailcap",
264 "marshal",
265 "math",
266 "mimetypes",
267 "mmap",
268 "modulefinder",
269 "msilib",
270 "msvcrt",
271 "multiprocessing",
272 "netrc",
273 "nis",
274 "nntplib",
275 "numbers",
276 "operator",
277 "optparse",
278 "os",
279 "ossaudiodev",
280 "parser",
281 "pathlib",
282 "pdb",
283 "pickle",
284 "pickletools",
285 "pipes",
286 "pkgutil",
287 "platform",
288 "plistlib",
289 "poplib",
290 "posix",
291 "pprint",
292 "profile",
293 "pstats",
294 "pty",
295 "pwd",
296 "py_compile",
297 "pyclbr",
298 "pydoc",
299 "queue",
300 "quopri",
301 "random",
302 "re",
303 "readline",
304 "reprlib",
305 "resource",
306 "rlcompleter",
307 "runpy",
308 "sched",
309 "secrets",
310 "select",
311 "selectors",
312 "shelve",
313 "shlex",
314 "shutil",
315 "signal",
316 "site",
317 "smtpd",
318 "smtplib",
319 "sndhdr",
320 "socket",
321 "socketserver",
322 "spwd",
323 "sqlite3",
324 "ssl",
325 "stat",
326 "statistics",
327 "string",
328 "stringprep",
329 "struct",
330 "subprocess",
331 "sunau",
332 "symbol",
333 "symtable",
334 "sys",
335 "sysconfig",
336 "syslog",
337 "tabnanny",
338 "tarfile",
339 "telnetlib",
340 "tempfile",
341 "termios",
342 "test",
343 "textwrap",
344 "threading",
345 "time",
346 "timeit",
347 "tkinter",
348 "token",
349 "tokenize",
350 "trace",
351 "traceback",
352 "tracemalloc",
353 "tty",
354 "turtle",
355 "turtledemo",
356 "types",
357 "typing",
358 "unicodedata",
359 "unittest",
360 "urllib",
361 "uu",
362 "uuid",
363 "venv",
364 "warnings",
365 "wave",
366 "weakref",
367 "webbrowser",
368 "winreg",
369 "winsound",
370 "wsgiref",
371 "xdrlib",
372 "xml",
373 "xmlrpc",
374 "zipapp",
375 "zipfile",
376 "zipimport",
377 "zlib",
378 "zoneinfo",
379 }
380
381 ####################################################################### 142 #######################################################################
382 ## Local imports 143 ## Local imports
383 ## 144 ##
384 ## adapted from: flake8-local-import v1.0.6 145 ## adapted from: flake8-local-import v1.0.6
385 ####################################################################### 146 #######################################################################
395 for violation in visitor.violations: 156 for violation in visitor.violations:
396 if not self.__ignoreCode(violation[1]): 157 if not self.__ignoreCode(violation[1]):
397 node = violation[0] 158 node = violation[0]
398 reason = violation[1] 159 reason = violation[1]
399 self.__error(node.lineno - 1, node.col_offset, reason) 160 self.__error(node.lineno - 1, node.col_offset, reason)
400
401 #######################################################################
402 ## Import order
403 ##
404 ## adapted from: flake8-alphabetize v0.0.18
405 #######################################################################
406
407 def __checkImportOrder(self):
408 """
409 Private method to check the order of import statements.
410 """
411 from .ImportNode import ImportNode
412
413 errors = []
414 imports = []
415 importNodes, listNode = self.__findNodes(self.__tree)
416
417 # check for an error in '__all__'
418 allError = self.__findErrorInAll(listNode)
419 if allError is not None:
420 errors.append(allError)
421
422 for importNode in importNodes:
423 if isinstance(importNode, ast.Import) and len(importNode.names) > 1:
424 # skip suck imports because its already handled by pycodestyle
425 continue
426
427 imports.append(
428 ImportNode(
429 self.__args.get("ApplicationPackageNames", []),
430 importNode,
431 self,
432 self.__args.get("SortIgnoringStyle", False),
433 self.__args.get("SortFromFirst", False),
434 )
435 )
436
437 lenImports = len(imports)
438 if lenImports > 0:
439 p = imports[0]
440 if p.error is not None:
441 errors.append(p.error)
442
443 if lenImports > 1:
444 for n in imports[1:]:
445 if n.error is not None:
446 errors.append(n.error)
447
448 if n == p:
449 if self.__args.get("CombinedAsImports", False) or (
450 not n.asImport and not p.asImport
451 ):
452 errors.append((n.node, "I203", str(p), str(n)))
453 elif n < p:
454 errors.append((n.node, "I201", str(n), str(p)))
455
456 p = n
457
458 for error in errors:
459 if not self.__ignoreCode(error[1]):
460 node = error[0]
461 reason = error[1]
462 args = error[2:]
463 self.__error(node.lineno - 1, node.col_offset, reason, *args)
464
465 def __findNodes(self, tree):
466 """
467 Private method to find all import and import from nodes of the given
468 tree.
469
470 @param tree reference to the ast node tree to be parsed
471 @type ast.AST
472 @return tuple containing a list of import nodes and the '__all__' node
473 @rtype tuple of (ast.Import | ast.ImportFrom, ast.List | ast.Tuple)
474 """
475 importNodes = []
476 listNode = None
477
478 if isinstance(tree, ast.Module):
479 body = tree.body
480
481 for n in body:
482 if isinstance(n, (ast.Import, ast.ImportFrom)):
483 importNodes.append(n)
484
485 elif isinstance(n, ast.Assign):
486 for t in n.targets:
487 if isinstance(t, ast.Name) and t.id == "__all__":
488 value = n.value
489
490 if isinstance(value, (ast.List, ast.Tuple)):
491 listNode = value
492
493 return importNodes, listNode
494
495 def __findErrorInAll(self, node):
496 """
497 Private method to check the '__all__' node for errors.
498
499 @param node reference to the '__all__' node
500 @type ast.List or ast.Tuple
501 @return tuple containing a reference to the node and an error code
502 @rtype rtype tuple of (ast.List | ast.Tuple, str)
503 """
504 if node is not None:
505 actualList = []
506 for el in node.elts:
507 if isinstance(el, ast.Constant):
508 actualList.append(el.value)
509 elif isinstance(el, ast.Str):
510 actualList.append(el.s)
511 else:
512 # Can't handle anything that isn't a string literal
513 return None
514
515 expectedList = self.sorted(
516 actualList,
517 key=lambda k: self.moduleKey(k, subImports=True),
518 )
519 if expectedList != actualList:
520 return (node, "I204", ", ".join(expectedList))
521
522 return None
523
524 def sorted(self, toSort, key=None, reverse=False):
525 """
526 Public method to sort the given list of names.
527
528 @param toSort list of names to be sorted
529 @type list of str
530 @param key function to generate keys (defaults to None)
531 @type function (optional)
532 @param reverse flag indicating a reverse sort (defaults to False)
533 @type bool (optional)
534 @return sorted list of names
535 @rtype list of str
536 """
537 return self.__sortingFunction(toSort, key=key, reverse=reverse)
538
539 def __naturally(self, toSort, key=None, reverse=False):
540 """
541 Private method to sort the given list of names naturally.
542
543 Note: Natural sorting maintains the sort order of numbers (i.e.
544 [Q1, Q10, Q2] is sorted as [Q1, Q2, Q10] while the Python
545 standard sort would yield [Q1, Q10, Q2].
546
547 @param toSort list of names to be sorted
548 @type list of str
549 @param key function to generate keys (defaults to None)
550 @type function (optional)
551 @param reverse flag indicating a reverse sort (defaults to False)
552 @type bool (optional)
553 @return sorted list of names
554 @rtype list of str
555 """
556 if key is None:
557 keyCallback = self.__naturalKeys
558 else:
559
560 def keyCallback(text):
561 return self.__naturalKeys(key(text))
562
563 return sorted(toSort, key=keyCallback, reverse=reverse)
564
565 def __atoi(self, text):
566 """
567 Private method to convert the given text to an integer number.
568
569 @param text text to be converted
570 @type str
571 @return integer number
572 @rtype int
573 """
574 return int(text) if text.isdigit() else text
575
576 def __naturalKeys(self, text):
577 """
578 Private method to generate keys for natural sorting.
579
580 @param text text to generate a key for
581 @type str
582 @return key for natural sorting
583 @rtype list of str or int
584 """
585 return [self.__atoi(c) for c in re.split(r"(\d+)", text)]
586
587 def moduleKey(self, moduleName, subImports=False):
588 """
589 Public method to generate a key for the given module name.
590
591 @param moduleName module name
592 @type str
593 @param subImports flag indicating a sub import like in
594 'from foo import bar, baz' (defaults to False)
595 @type bool (optional)
596 @return generated key
597 @rtype str
598 """
599 prefix = ""
600
601 if subImports:
602 if moduleName.isupper() and len(moduleName) > 1:
603 prefix = "A"
604 elif moduleName[0:1].isupper():
605 prefix = "B"
606 else:
607 prefix = "C"
608 if not self.__sortCaseSensitive:
609 moduleName = moduleName.lower()
610
611 return f"{prefix}{moduleName}"
612 161
613 ####################################################################### 162 #######################################################################
614 ## Tidy imports 163 ## Tidy imports
615 ## 164 ##
616 ## adapted from: flake8-tidy-imports v4.8.0 165 ## adapted from: flake8-tidy-imports v4.8.0

eric ide

mercurial