20 ## Local imports |
20 ## Local imports |
21 "I101", "I102", "I103", |
21 "I101", "I102", "I103", |
22 |
22 |
23 ## Imports order |
23 ## Imports order |
24 "I201", "I202", "I203", "I204", |
24 "I201", "I202", "I203", "I204", |
|
25 |
|
26 ## Various other import related |
|
27 "I901", "I902", "I903", "I904", |
25 ] |
28 ] |
26 |
29 |
27 def __init__(self, source, filename, tree, select, ignore, expected, |
30 def __init__(self, source, filename, tree, select, ignore, expected, |
28 repeat, args): |
31 repeat, args): |
29 """ |
32 """ |
61 # collection of detected errors |
64 # collection of detected errors |
62 self.errors = [] |
65 self.errors = [] |
63 |
66 |
64 checkersWithCodes = [ |
67 checkersWithCodes = [ |
65 (self.__checkLocalImports, ("I101", "I102", "I103")), |
68 (self.__checkLocalImports, ("I101", "I102", "I103")), |
66 (self.__checkImportOrder, ("I201", "I202", "I203", "I204")) |
69 (self.__checkImportOrder, ("I201", "I202", "I203", "I204")), |
|
70 (self.__tidyImports, ("I901", "I902", "I903", "I904")), |
67 ] |
71 ] |
68 |
72 |
69 self.__checkers = [] |
73 self.__checkers = [] |
70 for checker, codes in checkersWithCodes: |
74 for checker, codes in checkersWithCodes: |
71 if any(not (code and self.__ignoreCode(code)) |
75 if any(not (code and self.__ignoreCode(code)) |
319 expectedList = sorted(actualList) |
323 expectedList = sorted(actualList) |
320 if expectedList != actualList: |
324 if expectedList != actualList: |
321 return (node, "I204", ", ".join(expectedList)) |
325 return (node, "I204", ", ".join(expectedList)) |
322 |
326 |
323 return None |
327 return None |
|
328 |
|
329 ####################################################################### |
|
330 ## Tidy imports |
|
331 ## |
|
332 ## adapted from: flake8-tidy-imports v4.5.0 |
|
333 ####################################################################### |
|
334 |
|
335 def __tidyImports(self): |
|
336 """ |
|
337 Private method to check various other import related topics. |
|
338 """ |
|
339 self.__bannedModules = self.__args.get("BannedModules", []) |
|
340 self.__banRelativeImports = self.__args.get("BanRelativeImports", "") |
|
341 |
|
342 ruleMethods = [] |
|
343 if not self.__ignoreCode("I901"): |
|
344 ruleMethods.append(self.__checkUnnecessaryAlias) |
|
345 if ( |
|
346 not self.__ignoreCode("I902") |
|
347 and bool(self.__bannedModules) |
|
348 ): |
|
349 ruleMethods.append(self.__checkBannedImport) |
|
350 if ( |
|
351 (not self.__ignoreCode("I903") and |
|
352 self.__banRelativeImports == "parents") or |
|
353 (not self.__ignoreCode("I904") and |
|
354 self.__banRelativeImports == "true") |
|
355 ): |
|
356 ruleMethods.append(self.__checkBannedRelativeImports) |
|
357 |
|
358 for node in ast.walk(self.__tree): |
|
359 for method in ruleMethods: |
|
360 method(node) |
|
361 |
|
362 def __checkUnnecessaryAlias(self, node): |
|
363 """ |
|
364 Private method to check unnecessary import aliases. |
|
365 |
|
366 @param node reference to the node to be checked |
|
367 @type ast.AST |
|
368 """ |
|
369 if isinstance(node, ast.Import): |
|
370 for alias in node.names: |
|
371 if "." not in alias.name: |
|
372 fromName = None |
|
373 importedName = alias.name |
|
374 else: |
|
375 fromName, importedName = alias.name.rsplit(".", 1) |
|
376 |
|
377 if importedName == alias.asname: |
|
378 if fromName: |
|
379 rewritten = "from {0} import {1}".format( |
|
380 fromName, importedName) |
|
381 else: |
|
382 rewritten = "import {0}".format(importedName) |
|
383 |
|
384 self.__error(node.lineno - 1, node.col_offset, "I901", |
|
385 rewritten) |
|
386 |
|
387 elif isinstance(node, ast.ImportFrom): |
|
388 for alias in node.names: |
|
389 if alias.name == alias.asname: |
|
390 rewritten = "from {0} import {1}".format( |
|
391 node.module, alias.name) |
|
392 |
|
393 self.__error(node.lineno - 1, node.col_offset, "I901", |
|
394 rewritten) |
|
395 |
|
396 def __checkBannedImport(self, node): |
|
397 """ |
|
398 Private method to check import of banned modules. |
|
399 |
|
400 @param node reference to the node to be checked |
|
401 @type ast.AST |
|
402 """ |
|
403 if not bool(self.__bannedModules): |
|
404 return |
|
405 |
|
406 if isinstance(node, ast.Import): |
|
407 moduleNames = [alias.name for alias in node.names] |
|
408 elif isinstance(node, ast.ImportFrom): |
|
409 nodeModule = node.module or "" |
|
410 moduleNames = [nodeModule] |
|
411 for alias in node.names: |
|
412 moduleNames.append("{0}.{1}".format(nodeModule, alias.name)) |
|
413 else: |
|
414 return |
|
415 |
|
416 # Sort from most to least specific paths. |
|
417 moduleNames.sort(key=len, reverse=True) |
|
418 |
|
419 warned = set() |
|
420 |
|
421 for moduleName in moduleNames: |
|
422 if moduleName in self.__bannedModules: |
|
423 if any(mod.startswith(moduleName) for mod in warned): |
|
424 # Do not show an error for this line if we already showed |
|
425 # a more specific error. |
|
426 continue |
|
427 else: |
|
428 warned.add(moduleName) |
|
429 self.__error(node.lineno - 1, node.col_offset, "I902", |
|
430 moduleName) |
|
431 |
|
432 def __checkBannedRelativeImports(self, node): |
|
433 """ |
|
434 Private method to check if relative imports are banned. |
|
435 |
|
436 @param node reference to the node to be checked |
|
437 @type ast.AST |
|
438 """ |
|
439 if not self.__banRelativeImports: |
|
440 return |
|
441 |
|
442 elif self.__banRelativeImports == "parents": |
|
443 minNodeLevel = 1 |
|
444 msgCode = "I903" |
|
445 else: |
|
446 minNodeLevel = 0 |
|
447 msgCode = "I904" |
|
448 |
|
449 if ( |
|
450 self.__banRelativeImports and |
|
451 isinstance(node, ast.ImportFrom) and |
|
452 node.level > minNodeLevel |
|
453 ): |
|
454 self.__error(node.lineno - 1, node.col_offset, msgCode) |