"Fossies" - the Fresh Open Source Software Archive

Member "eric6-20.9/eric/eric6/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py" (1 Aug 2020, 80895 Bytes) of package /linux/misc/eric6-20.9.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "MiscellaneousChecker.py" see the Fossies "Dox" file reference documentation.

    1 # -*- coding: utf-8 -*-
    2 
    3 # Copyright (c) 2015 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
    4 #
    5 
    6 """
    7 Module implementing a checker for miscellaneous checks.
    8 """
    9 
   10 import sys
   11 import ast
   12 import re
   13 import itertools
   14 from string import Formatter
   15 from collections import defaultdict
   16 import tokenize
   17 
   18 import AstUtilities
   19 
   20 
   21 def composeCallPath(node):
   22     """
   23     Generator function to assemble the call path of a given node.
   24     
   25     @param node node to assemble call path for
   26     @type ast.Node
   27     @return call path components
   28     @rtype str
   29     """
   30     if isinstance(node, ast.Attribute):
   31         for v in composeCallPath(node.value):
   32             yield v
   33         yield node.attr
   34     elif isinstance(node, ast.Name):
   35         yield node.id
   36 
   37 
   38 class MiscellaneousChecker(object):
   39     """
   40     Class implementing a checker for miscellaneous checks.
   41     """
   42     Codes = [
   43         ## Coding line
   44         "M101", "M102",
   45         
   46         ## Copyright
   47         "M111", "M112",
   48         
   49         ## Shadowed Builtins
   50         "M131", "M132",
   51         
   52         ## Comprehensions
   53         "M181", "M182", "M183", "M184",
   54         "M185", "M186", "M187",
   55         "M191", "M192", "M193",
   56         "M195", "M196", "M197", "M198",
   57         
   58         ## Dictionaries with sorted keys
   59         "M201",
   60         
   61         ## Naive datetime usage
   62         "M301", "M302", "M303", "M304", "M305", "M306", "M307", "M308",
   63         "M311", "M312", "M313", "M314", "M315",
   64         "M321",
   65         
   66         ## sys.version and sys.version_info usage
   67         "M401", "M402", "M403",
   68         "M411", "M412", "M413", "M414",
   69         "M421", "M422", "M423",
   70         
   71         ## Bugbear
   72         "M501", "M502", "M503", "M504", "M505", "M506", "M507", "M508",
   73         "M509",
   74         "M511", "M512", "M513",
   75         "M521", "M522", "M523", "M524",
   76         
   77         ## Format Strings
   78         "M601",
   79         "M611", "M612", "M613",
   80         "M621", "M622", "M623", "M624", "M625",
   81         "M631", "M632",
   82         
   83         ## Logging
   84         "M651", "M652", "M653", "M654", "M655",
   85         
   86         ## Future statements
   87         "M701", "M702",
   88         
   89         ## Gettext
   90         "M711",
   91         
   92         ## print
   93         "M801",
   94         
   95         ## one element tuple
   96         "M811",
   97         
   98         ## Mutable Defaults
   99         "M821", "M822",
  100         
  101         ## return statements
  102         "M831", "M832", "M833", "M834",
  103         
  104         ## line continuation
  105         "M841",
  106         
  107         ## commented code
  108         "M891",
  109         
  110         ## syntax error
  111         "M901",
  112     ]
  113     
  114     Formatter = Formatter()
  115     FormatFieldRegex = re.compile(r'^((?:\s|.)*?)(\..*|\[.*\])?$')
  116     
  117     BuiltinsWhiteList = [
  118         "__name__",
  119         "__doc__",
  120         "credits",
  121     ]
  122 
  123     def __init__(self, source, filename, select, ignore, expected, repeat,
  124                  args):
  125         """
  126         Constructor
  127         
  128         @param source source code to be checked
  129         @type list of str
  130         @param filename name of the source file
  131         @type str
  132         @param select list of selected codes
  133         @type list of str
  134         @param ignore list of codes to be ignored
  135         @type list of str
  136         @param expected list of expected codes
  137         @type list of str
  138         @param repeat flag indicating to report each occurrence of a code
  139         @type bool
  140         @param args dictionary of arguments for the miscellaneous checks
  141         @type dict
  142         """
  143         self.__select = tuple(select)
  144         self.__ignore = ('',) if select else tuple(ignore)
  145         self.__expected = expected[:]
  146         self.__repeat = repeat
  147         self.__filename = filename
  148         self.__source = source[:]
  149         self.__args = args
  150         
  151         self.__pep3101FormatRegex = re.compile(
  152             r'^(?:[^\'"]*[\'"][^\'"]*[\'"])*\s*%|^\s*%')
  153         
  154         import builtins
  155         self.__builtins = [b for b in dir(builtins)
  156                            if b not in self.BuiltinsWhiteList]
  157 
  158         # statistics counters
  159         self.counters = {}
  160         
  161         # collection of detected errors
  162         self.errors = []
  163         
  164         checkersWithCodes = [
  165             (self.__checkCoding, ("M101", "M102")),
  166             (self.__checkCopyright, ("M111", "M112")),
  167             (self.__checkBuiltins, ("M131", "M132")),
  168             (self.__checkComprehensions, ("M181", "M182", "M183", "M184",
  169                                           "M185", "M186", "M187",
  170                                           "M191", "M192", "M193",
  171                                           "M195", "M196", "M197", "M198")),
  172             (self.__checkDictWithSortedKeys, ("M201",)),
  173             (self.__checkDateTime, ("M301", "M302", "M303", "M304", "M305",
  174                                     "M306", "M307", "M308", "M311", "M312",
  175                                     "M313", "M314", "M315", "M321")),
  176             (self.__checkSysVersion, ("M401", "M402", "M403",
  177                                       "M411", "M412", "M413", "M414",
  178                                       "M421", "M422", "M423")),
  179             (self.__checkBugBear, ("M501", "M502", "M503", "M504", "M505",
  180                                    "M506", "M507", "M508", "M509",
  181                                    "M511", "M512", "M513",
  182                                    "M521", "M522", "M523", "M524")),
  183             (self.__checkPep3101, ("M601",)),
  184             (self.__checkFormatString, ("M611", "M612", "M613",
  185                                         "M621", "M622", "M623", "M624", "M625",
  186                                         "M631", "M632")),
  187             (self.__checkLogging, ("M651", "M652", "M653", "M654", "M655")),
  188             (self.__checkFuture, ("M701", "M702")),
  189             (self.__checkGettext, ("M711",)),
  190             (self.__checkPrintStatements, ("M801",)),
  191             (self.__checkTuple, ("M811",)),
  192             (self.__checkMutableDefault, ("M821", "M822")),
  193             (self.__checkReturn, ("M831", "M832", "M833", "M834")),
  194             (self.__checkLineContinuation, ("M841",)),
  195             (self.__checkCommentedCode, ("M891",)),
  196         ]
  197         
  198         self.__defaultArgs = {
  199             "BuiltinsChecker": {
  200                 "chr": ["unichr", ],
  201                 "str": ["unicode", ],
  202             },
  203             "CodingChecker": 'latin-1, utf-8',
  204             "CopyrightChecker": {
  205                 "Author": "",
  206                 "MinFilesize": 0,
  207             },
  208             "CommentedCodeChecker": {
  209                 "Aggressive": False,
  210             }
  211         }
  212         
  213         self.__checkers = []
  214         for checker, codes in checkersWithCodes:
  215             if any(not (code and self.__ignoreCode(code))
  216                     for code in codes):
  217                 self.__checkers.append(checker)
  218     
  219     def __ignoreCode(self, code):
  220         """
  221         Private method to check if the message code should be ignored.
  222 
  223         @param code message code to check for
  224         @type str
  225         @return flag indicating to ignore the given code
  226         @rtype bool
  227         """
  228         return (code.startswith(self.__ignore) and
  229                 not code.startswith(self.__select))
  230     
  231     def __error(self, lineNumber, offset, code, *args):
  232         """
  233         Private method to record an issue.
  234         
  235         @param lineNumber line number of the issue
  236         @type int
  237         @param offset position within line of the issue
  238         @type int
  239         @param code message code
  240         @type str
  241         @param args arguments for the message
  242         @type list
  243         """
  244         if self.__ignoreCode(code):
  245             return
  246         
  247         if code in self.counters:
  248             self.counters[code] += 1
  249         else:
  250             self.counters[code] = 1
  251         
  252         # Don't care about expected codes
  253         if code in self.__expected:
  254             return
  255         
  256         if code and (self.counters[code] == 1 or self.__repeat):
  257             # record the issue with one based line number
  258             self.errors.append(
  259                 {
  260                     "file": self.__filename,
  261                     "line": lineNumber + 1,
  262                     "offset": offset,
  263                     "code": code,
  264                     "args": args,
  265                 }
  266             )
  267     
  268     def __reportInvalidSyntax(self):
  269         """
  270         Private method to report a syntax error.
  271         """
  272         exc_type, exc = sys.exc_info()[:2]
  273         if len(exc.args) > 1:
  274             offset = exc.args[1]
  275             if len(offset) > 2:
  276                 offset = offset[1:3]
  277         else:
  278             offset = (1, 0)
  279         self.__error(offset[0] - 1, offset[1] or 0,
  280                      'M901', exc_type.__name__, exc.args[0])
  281     
  282     def __generateTree(self):
  283         """
  284         Private method to generate an AST for our source.
  285         
  286         @return generated AST
  287         @rtype ast.AST
  288         """
  289         source = "".join(self.__source)
  290         return compile(source, self.__filename, 'exec', ast.PyCF_ONLY_AST)
  291     
  292     def run(self):
  293         """
  294         Public method to check the given source against miscellaneous
  295         conditions.
  296         """
  297         if not self.__filename:
  298             # don't do anything, if essential data is missing
  299             return
  300         
  301         if not self.__checkers:
  302             # don't do anything, if no codes were selected
  303             return
  304         
  305         try:
  306             self.__tree = self.__generateTree()
  307         except (SyntaxError, TypeError):
  308             self.__reportInvalidSyntax()
  309             return
  310         
  311         for check in self.__checkers:
  312             check()
  313     
  314     def __getCoding(self):
  315         """
  316         Private method to get the defined coding of the source.
  317         
  318         @return tuple containing the line number and the coding
  319         @rtype tuple of int and str
  320         """
  321         for lineno, line in enumerate(self.__source[:5]):
  322             matched = re.search(r'coding[:=]\s*([-\w_.]+)',
  323                                 line, re.IGNORECASE)
  324             if matched:
  325                 return lineno, matched.group(1)
  326         else:
  327             return 0, ""
  328     
  329     def __checkCoding(self):
  330         """
  331         Private method to check the presence of a coding line and valid
  332         encodings.
  333         """
  334         if len(self.__source) == 0:
  335             return
  336         
  337         encodings = [e.lower().strip()
  338                      for e in self.__args.get(
  339                      "CodingChecker", self.__defaultArgs["CodingChecker"])
  340                      .split(",")]
  341         lineno, coding = self.__getCoding()
  342         if coding:
  343             if coding.lower() not in encodings:
  344                 self.__error(lineno, 0, "M102", coding)
  345         else:
  346             self.__error(0, 0, "M101")
  347     
  348     def __checkCopyright(self):
  349         """
  350         Private method to check the presence of a copyright statement.
  351         """
  352         source = "".join(self.__source)
  353         copyrightArgs = self.__args.get(
  354             "CopyrightChecker", self.__defaultArgs["CopyrightChecker"])
  355         copyrightMinFileSize = copyrightArgs.get(
  356             "MinFilesize",
  357             self.__defaultArgs["CopyrightChecker"]["MinFilesize"])
  358         copyrightAuthor = copyrightArgs.get(
  359             "Author",
  360             self.__defaultArgs["CopyrightChecker"]["Author"])
  361         copyrightRegexStr = (
  362             r"Copyright\s+(\(C\)\s+)?(\d{{4}}\s+-\s+)?\d{{4}}\s+{author}"
  363         )
  364         
  365         tocheck = max(1024, copyrightMinFileSize)
  366         topOfSource = source[:tocheck]
  367         if len(topOfSource) < copyrightMinFileSize:
  368             return
  369 
  370         copyrightRe = re.compile(copyrightRegexStr.format(author=r".*"),
  371                                  re.IGNORECASE)
  372         if not copyrightRe.search(topOfSource):
  373             self.__error(0, 0, "M111")
  374             return
  375         
  376         if copyrightAuthor:
  377             copyrightAuthorRe = re.compile(
  378                 copyrightRegexStr.format(author=copyrightAuthor),
  379                 re.IGNORECASE)
  380             if not copyrightAuthorRe.search(topOfSource):
  381                 self.__error(0, 0, "M112")
  382     
  383     def __checkCommentedCode(self):
  384         """
  385         Private method to check for commented code.
  386         """
  387         from eradicate import commented_out_code_line_numbers
  388         
  389         source = "".join(self.__source)
  390         commentedCodeCheckerArgs = self.__args.get(
  391             "CommentedCodeChecker", self.__defaultArgs["CommentedCodeChecker"])
  392         aggressive = commentedCodeCheckerArgs.get(
  393             "Aggressive",
  394             self.__defaultArgs["CommentedCodeChecker"]["Aggressive"])
  395         for markedLine in commented_out_code_line_numbers(
  396                 source, aggressive=aggressive):
  397             self.__error(markedLine - 1, 0, "M891")
  398     
  399     def __checkLineContinuation(self):
  400         """
  401         Private method to check line continuation using backslash.
  402         """
  403         # generate source lines without comments
  404         linesIterator = iter(self.__source)
  405         tokens = tokenize.generate_tokens(lambda: next(linesIterator))
  406         comments = [token for token in tokens if token[0] == tokenize.COMMENT]
  407         stripped = self.__source[:]
  408         for comment in comments:
  409             lineno = comment[3][0]
  410             start = comment[2][1]
  411             stop = comment[3][1]
  412             content = stripped[lineno - 1]
  413             withoutComment = content[:start] + content[stop:]
  414             stripped[lineno - 1] = withoutComment.rstrip()
  415         
  416         # perform check with 'cleaned' source
  417         for lineIndex, line in enumerate(stripped):
  418             strippedLine = line.strip()
  419             if (strippedLine.endswith('\\') and
  420                     not strippedLine.startswith(('assert', 'with'))):
  421                 self.__error(lineIndex, len(line), "M841")
  422     
  423     def __checkPrintStatements(self):
  424         """
  425         Private method to check for print statements.
  426         """
  427         for node in ast.walk(self.__tree):
  428             if (
  429                 (isinstance(node, ast.Call) and
  430                  getattr(node.func, 'id', None) == 'print') or
  431                 (hasattr(ast, 'Print') and isinstance(node, ast.Print))
  432             ):
  433                 self.__error(node.lineno - 1, node.col_offset, "M801")
  434     
  435     def __checkTuple(self):
  436         """
  437         Private method to check for one element tuples.
  438         """
  439         for node in ast.walk(self.__tree):
  440             if (
  441                 isinstance(node, ast.Tuple) and
  442                 len(node.elts) == 1
  443             ):
  444                 self.__error(node.lineno - 1, node.col_offset, "M811")
  445     
  446     def __checkFuture(self):
  447         """
  448         Private method to check the __future__ imports.
  449         """
  450         expectedImports = {
  451             i.strip()
  452             for i in self.__args.get("FutureChecker", "").split(",")
  453             if bool(i.strip())}
  454         if len(expectedImports) == 0:
  455             # nothing to check for; disabling the check
  456             return
  457         
  458         imports = set()
  459         node = None
  460         hasCode = False
  461         
  462         for node in ast.walk(self.__tree):
  463             if (isinstance(node, ast.ImportFrom) and
  464                     node.module == '__future__'):
  465                 imports |= {name.name for name in node.names}
  466             elif isinstance(node, ast.Expr):
  467                 if not AstUtilities.isString(node.value):
  468                     hasCode = True
  469                     break
  470             elif not (
  471                 AstUtilities.isString(node) or
  472                 isinstance(node, ast.Module)
  473             ):
  474                 hasCode = True
  475                 break
  476 
  477         if isinstance(node, ast.Module) or not hasCode:
  478             return
  479 
  480         if not (imports >= expectedImports):
  481             if imports:
  482                 self.__error(node.lineno - 1, node.col_offset, "M701",
  483                              ", ".join(expectedImports), ", ".join(imports))
  484             else:
  485                 self.__error(node.lineno - 1, node.col_offset, "M702",
  486                              ", ".join(expectedImports))
  487     
  488     def __checkPep3101(self):
  489         """
  490         Private method to check for old style string formatting.
  491         """
  492         for lineno, line in enumerate(self.__source):
  493             match = self.__pep3101FormatRegex.search(line)
  494             if match:
  495                 lineLen = len(line)
  496                 pos = line.find('%')
  497                 formatPos = pos
  498                 formatter = '%'
  499                 if line[pos + 1] == "(":
  500                     pos = line.find(")", pos)
  501                 c = line[pos]
  502                 while c not in "diouxXeEfFgGcrs":
  503                     pos += 1
  504                     if pos >= lineLen:
  505                         break
  506                     c = line[pos]
  507                 if c in "diouxXeEfFgGcrs":
  508                     formatter += c
  509                 self.__error(lineno, formatPos, "M601", formatter)
  510     
  511     def __checkFormatString(self):
  512         """
  513         Private method to check string format strings.
  514         """
  515         coding = self.__getCoding()[1]
  516         if not coding:
  517             # default to utf-8
  518             coding = "utf-8"
  519         
  520         visitor = TextVisitor()
  521         visitor.visit(self.__tree)
  522         for node in visitor.nodes:
  523             text = node.s
  524             if isinstance(text, bytes):
  525                 try:
  526                     text = text.decode(coding)
  527                 except UnicodeDecodeError:
  528                     continue
  529             fields, implicit, explicit = self.__getFields(text)
  530             if implicit:
  531                 if node in visitor.calls:
  532                     self.__error(node.lineno - 1, node.col_offset, "M611")
  533                 else:
  534                     if node.is_docstring:
  535                         self.__error(node.lineno - 1, node.col_offset, "M612")
  536                     else:
  537                         self.__error(node.lineno - 1, node.col_offset, "M613")
  538             
  539             if node in visitor.calls:
  540                 call, strArgs = visitor.calls[node]
  541                 
  542                 numbers = set()
  543                 names = set()
  544                 # Determine which fields require a keyword and which an arg
  545                 for name in fields:
  546                     fieldMatch = self.FormatFieldRegex.match(name)
  547                     try:
  548                         number = int(fieldMatch.group(1))
  549                     except ValueError:
  550                         number = -1
  551                     # negative numbers are considered keywords
  552                     if number >= 0:
  553                         numbers.add(number)
  554                     else:
  555                         names.add(fieldMatch.group(1))
  556                 
  557                 keywords = {keyword.arg for keyword in call.keywords}
  558                 numArgs = len(call.args)
  559                 if strArgs:
  560                     numArgs -= 1
  561                 if sys.version_info < (3, 5):
  562                     hasKwArgs = bool(call.kwargs)
  563                     hasStarArgs = bool(call.starargs)
  564                 else:
  565                     hasKwArgs = any(kw.arg is None for kw in call.keywords)
  566                     hasStarArgs = sum(1 for arg in call.args
  567                                       if isinstance(arg, ast.Starred))
  568                     
  569                     if hasKwArgs:
  570                         keywords.discard(None)
  571                     if hasStarArgs:
  572                         numArgs -= 1
  573                 
  574                 # if starargs or kwargs is not None, it can't count the
  575                 # parameters but at least check if the args are used
  576                 if hasKwArgs:
  577                     if not names:
  578                         # No names but kwargs
  579                         self.__error(call.lineno - 1, call.col_offset, "M623")
  580                 if hasStarArgs:
  581                     if not numbers:
  582                         # No numbers but args
  583                         self.__error(call.lineno - 1, call.col_offset, "M624")
  584                 
  585                 if not hasKwArgs and not hasStarArgs:
  586                     # can actually verify numbers and names
  587                     for number in sorted(numbers):
  588                         if number >= numArgs:
  589                             self.__error(call.lineno - 1, call.col_offset,
  590                                          "M621", number)
  591                     
  592                     for name in sorted(names):
  593                         if name not in keywords:
  594                             self.__error(call.lineno - 1, call.col_offset,
  595                                          "M622", name)
  596                 
  597                 for arg in range(numArgs):
  598                     if arg not in numbers:
  599                         self.__error(call.lineno - 1, call.col_offset, "M631",
  600                                      arg)
  601                 
  602                 for keyword in keywords:
  603                     if keyword not in names:
  604                         self.__error(call.lineno - 1, call.col_offset, "M632",
  605                                      keyword)
  606                 
  607                 if implicit and explicit:
  608                     self.__error(call.lineno - 1, call.col_offset, "M625")
  609     
  610     def __getFields(self, string):
  611         """
  612         Private method to extract the format field information.
  613         
  614         @param string format string to be parsed
  615         @type str
  616         @return format field information as a tuple with fields, implicit
  617             field definitions present and explicit field definitions present
  618         @rtype tuple of set of str, bool, bool
  619         """
  620         fields = set()
  621         cnt = itertools.count()
  622         implicit = False
  623         explicit = False
  624         try:
  625             for _literal, field, spec, conv in self.Formatter.parse(string):
  626                 if field is not None and (conv is None or conv in 'rsa'):
  627                     if not field:
  628                         field = str(next(cnt))
  629                         implicit = True
  630                     else:
  631                         explicit = True
  632                     fields.add(field)
  633                     fields.update(parsedSpec[1]
  634                                   for parsedSpec in self.Formatter.parse(spec)
  635                                   if parsedSpec[1] is not None)
  636         except ValueError:
  637             return set(), False, False
  638         else:
  639             return fields, implicit, explicit
  640     
  641     def __checkBuiltins(self):
  642         """
  643         Private method to check, if built-ins are shadowed.
  644         """
  645         functionDefs = [ast.FunctionDef]
  646         try:
  647             functionDefs.append(ast.AsyncFunctionDef)
  648         except AttributeError:
  649             pass
  650         
  651         ignoreBuiltinAssignments = self.__args.get(
  652             "BuiltinsChecker", self.__defaultArgs["BuiltinsChecker"])
  653         
  654         for node in ast.walk(self.__tree):
  655             if isinstance(node, ast.Assign):
  656                 # assign statement
  657                 for element in node.targets:
  658                     if (
  659                         isinstance(element, ast.Name) and
  660                         element.id in self.__builtins
  661                     ):
  662                         value = node.value
  663                         if (
  664                             isinstance(value, ast.Name) and
  665                             element.id in ignoreBuiltinAssignments and
  666                             value.id in ignoreBuiltinAssignments[element.id]
  667                         ):
  668                             # ignore compatibility assignments
  669                             continue
  670                         self.__error(element.lineno - 1, element.col_offset,
  671                                      "M131", element.id)
  672                     elif isinstance(element, (ast.Tuple, ast.List)):
  673                         for tupleElement in element.elts:
  674                             if (
  675                                 isinstance(tupleElement, ast.Name) and
  676                                 tupleElement.id in self.__builtins
  677                             ):
  678                                 self.__error(tupleElement.lineno - 1,
  679                                              tupleElement.col_offset,
  680                                              "M131", tupleElement.id)
  681             elif isinstance(node, ast.For):
  682                 # for loop
  683                 target = node.target
  684                 if (
  685                     isinstance(target, ast.Name) and
  686                     target.id in self.__builtins
  687                 ):
  688                     self.__error(target.lineno - 1, target.col_offset,
  689                                  "M131", target.id)
  690                 elif isinstance(target, (ast.Tuple, ast.List)):
  691                     for element in target.elts:
  692                         if (
  693                             isinstance(element, ast.Name) and
  694                             element.id in self.__builtins
  695                         ):
  696                             self.__error(element.lineno - 1,
  697                                          element.col_offset,
  698                                          "M131", element.id)
  699             elif any(isinstance(node, functionDef)
  700                      for functionDef in functionDefs):
  701                 # (asynchronous) function definition
  702                 for arg in node.args.args:
  703                     if (
  704                         isinstance(arg, ast.arg) and
  705                         arg.arg in self.__builtins
  706                     ):
  707                         self.__error(arg.lineno - 1, arg.col_offset,
  708                                      "M132", arg.arg)
  709     
  710     def __checkComprehensions(self):
  711         """
  712         Private method to check some comprehension related things.
  713         """
  714         for node in ast.walk(self.__tree):
  715             if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
  716                 nArgs = len(node.args)
  717                 
  718                 if (
  719                     nArgs == 1 and
  720                     isinstance(node.args[0], ast.GeneratorExp) and
  721                     node.func.id in ('list', 'set')
  722                 ):
  723                     errorCode = {
  724                         "list": "M181",
  725                         "set": "M182",
  726                     }[node.func.id]
  727                     self.__error(node.lineno - 1, node.col_offset, errorCode)
  728 
  729                 elif (
  730                     nArgs == 1 and
  731                     isinstance(node.args[0], ast.GeneratorExp) and
  732                     isinstance(node.args[0].elt, ast.Tuple) and
  733                     len(node.args[0].elt.elts) == 2 and
  734                     node.func.id == "dict"
  735                 ):
  736                     self.__error(node.lineno - 1, node.col_offset, "M183")
  737                 
  738                 elif (
  739                     nArgs == 1 and
  740                     isinstance(node.args[0], ast.ListComp) and
  741                     node.func.id in ('list', 'set', 'dict')
  742                 ):
  743                     errorCode = {
  744                         'list': 'M195',
  745                         'dict': 'M185',
  746                         'set': 'M184',
  747                     }[node.func.id]
  748                     self.__error(node.lineno - 1, node.col_offset, errorCode)
  749                 
  750                 elif nArgs == 1 and (
  751                     isinstance(node.args[0], ast.Tuple) and
  752                     node.func.id == "tuple" or
  753                     isinstance(node.args[0], ast.List) and
  754                     node.func.id == "list"
  755                 ):
  756                     errorCode = {
  757                         'tuple': 'M197',
  758                         'list': 'M198',
  759                     }[node.func.id]
  760                     self.__error(node.lineno - 1, node.col_offset, errorCode,
  761                                  type(node.args[0]).__name__.lower(),
  762                                  node.func.id)
  763                 
  764                 elif (
  765                     nArgs == 1 and
  766                     isinstance(node.args[0], (ast.Tuple, ast.List)) and
  767                     node.func.id in ("tuple", "list", "set", "dict")
  768                 ):
  769                     errorCode = {
  770                         "tuple": "M192",
  771                         "list": "M193",
  772                         "set": "M191",
  773                         "dict": "M191",
  774                     }[node.func.id]
  775                     self.__error(node.lineno - 1, node.col_offset, errorCode,
  776                                  type(node.args[0]).__name__.lower(),
  777                                  node.func.id)
  778 
  779                 elif (
  780                     nArgs == 1 and
  781                     isinstance(node.args[0], ast.ListComp) and
  782                     node.func.id in ('all', 'any', 'enumerate', 'frozenset',
  783                                      'max', 'min', 'sorted', 'sum', 'tuple',)
  784                 ):
  785                     self.__error(node.lineno - 1, node.col_offset, "M187",
  786                                  node.func.id)
  787                 
  788                 elif (
  789                     nArgs == 0 and
  790                     not any(isinstance(a, ast.Starred) for a in node.args) and
  791                     not any(k.arg is None for k in node.keywords) and
  792                     node.func.id in ("tuple", "list", "dict")
  793                 ):
  794                     self.__error(node.lineno - 1, node.col_offset, "M186",
  795                                  node.func.id)
  796                 
  797                 elif isinstance(node, ast.Compare) and (
  798                     len(node.ops) == 1 and
  799                     isinstance(node.ops[0], ast.In) and
  800                     len(node.comparators) == 1 and
  801                     isinstance(node.comparators[0], ast.ListComp)
  802                 ):
  803                     self.__error(node.lineno - 1, node.col_offset, "M196")
  804     
  805     def __checkMutableDefault(self):
  806         """
  807         Private method to check for use of mutable types as default arguments.
  808         """
  809         mutableTypes = (
  810             ast.Call,
  811             ast.Dict,
  812             ast.List,
  813             ast.Set,
  814         )
  815         mutableCalls = (
  816             "Counter",
  817             "OrderedDict",
  818             "collections.Counter",
  819             "collections.OrderedDict",
  820             "collections.defaultdict",
  821             "collections.deque",
  822             "defaultdict",
  823             "deque",
  824             "dict",
  825             "list",
  826             "set",
  827         )
  828         immutableCalls = (
  829             "tuple",
  830             "frozenset",
  831         )
  832         functionDefs = [ast.FunctionDef]
  833         try:
  834             functionDefs.append(ast.AsyncFunctionDef)
  835         except AttributeError:
  836             pass
  837         
  838         for node in ast.walk(self.__tree):
  839             if any(isinstance(node, functionDef)
  840                    for functionDef in functionDefs):
  841                 defaults = node.args.defaults[:]
  842                 try:
  843                     defaults += node.args.kw_defaults[:]
  844                 except AttributeError:
  845                     pass
  846                 for default in defaults:
  847                     if any(isinstance(default, mutableType)
  848                            for mutableType in mutableTypes):
  849                         typeName = type(default).__name__
  850                         if isinstance(default, ast.Call):
  851                             callPath = '.'.join(composeCallPath(default.func))
  852                             if callPath in mutableCalls:
  853                                 self.__error(default.lineno - 1,
  854                                              default.col_offset,
  855                                              "M823", callPath + "()")
  856                             elif callPath not in immutableCalls:
  857                                 self.__error(default.lineno - 1,
  858                                              default.col_offset,
  859                                              "M822", typeName)
  860                         else:
  861                             self.__error(default.lineno - 1,
  862                                          default.col_offset,
  863                                          "M821", typeName)
  864     
  865     def __dictShouldBeChecked(self, node):
  866         """
  867         Private function to test, if the node should be checked.
  868         
  869         @param node reference to the AST node
  870         @return flag indicating to check the node
  871         @rtype bool
  872         """
  873         if not all(AstUtilities.isString(key) for key in node.keys):
  874             return False
  875         
  876         if (
  877             "__IGNORE_WARNING__" in self.__source[node.lineno - 1] or
  878             "__IGNORE_WARNING_M201__" in self.__source[node.lineno - 1]
  879         ):
  880             return False
  881         
  882         lineNumbers = [key.lineno for key in node.keys]
  883         return len(lineNumbers) == len(set(lineNumbers))
  884     
  885     def __checkDictWithSortedKeys(self):
  886         """
  887         Private method to check, if dictionary keys appear in sorted order.
  888         """
  889         for node in ast.walk(self.__tree):
  890             if isinstance(node, ast.Dict) and self.__dictShouldBeChecked(node):
  891                 for key1, key2 in zip(node.keys, node.keys[1:]):
  892                     if key2.s < key1.s:
  893                         self.__error(key2.lineno - 1, key2.col_offset,
  894                                      "M201", key2.s, key1.s)
  895     
  896     def __checkLogging(self):
  897         """
  898         Private method to check logging statements.
  899         """
  900         visitor = LoggingVisitor()
  901         visitor.visit(self.__tree)
  902         for node, reason in visitor.violations:
  903             self.__error(node.lineno - 1, node.col_offset, reason)
  904     
  905     def __checkGettext(self):
  906         """
  907         Private method to check the 'gettext' import statement.
  908         """
  909         for node in ast.walk(self.__tree):
  910             if (
  911                 isinstance(node, ast.ImportFrom) and
  912                 any(name.asname == '_' for name in node.names)
  913             ):
  914                 self.__error(node.lineno - 1, node.col_offset, "M711",
  915                              node.names[0].name)
  916     
  917     def __checkBugBear(self):
  918         """
  919         Private method for bugbear checks.
  920         """
  921         visitor = BugBearVisitor()
  922         visitor.visit(self.__tree)
  923         for violation in visitor.violations:
  924             node = violation[0]
  925             reason = violation[1]
  926             params = violation[2:]
  927             self.__error(node.lineno - 1, node.col_offset, reason, *params)
  928     
  929     def __checkReturn(self):
  930         """
  931         Private method to check return statements.
  932         """
  933         visitor = ReturnVisitor()
  934         visitor.visit(self.__tree)
  935         for violation in visitor.violations:
  936             node = violation[0]
  937             reason = violation[1]
  938             self.__error(node.lineno - 1, node.col_offset, reason)
  939     
  940     def __checkDateTime(self):
  941         """
  942         Private method to check use of naive datetime functions.
  943         """
  944         # step 1: generate an augmented node tree containing parent info
  945         #         for each child node
  946         tree = self.__generateTree()
  947         for node in ast.walk(tree):
  948             for childNode in ast.iter_child_nodes(node):
  949                 childNode._dtCheckerParent = node
  950         
  951         # step 2: perform checks and report issues
  952         visitor = DateTimeVisitor()
  953         visitor.visit(tree)
  954         for violation in visitor.violations:
  955             node = violation[0]
  956             reason = violation[1]
  957             self.__error(node.lineno - 1, node.col_offset, reason)
  958     
  959     def __checkSysVersion(self):
  960         """
  961         Private method to check the use of sys.version and sys.version_info.
  962         """
  963         visitor = SysVersionVisitor()
  964         visitor.visit(self.__tree)
  965         for violation in visitor.violations:
  966             node = violation[0]
  967             reason = violation[1]
  968             self.__error(node.lineno - 1, node.col_offset, reason)
  969 
  970 
  971 class TextVisitor(ast.NodeVisitor):
  972     """
  973     Class implementing a node visitor for bytes and str instances.
  974 
  975     It tries to detect docstrings as string of the first expression of each
  976     module, class or function.
  977     """
  978     # modelled after the string format flake8 extension
  979     
  980     def __init__(self):
  981         """
  982         Constructor
  983         """
  984         super(TextVisitor, self).__init__()
  985         self.nodes = []
  986         self.calls = {}
  987 
  988     def __addNode(self, node):
  989         """
  990         Private method to add a node to our list of nodes.
  991         
  992         @param node reference to the node to add
  993         @type ast.AST
  994         """
  995         if not hasattr(node, 'is_docstring'):
  996             node.is_docstring = False
  997         self.nodes.append(node)
  998 
  999     def visit_Str(self, node):
 1000         """
 1001         Public method to record a string node.
 1002         
 1003         @param node reference to the string node
 1004         @type ast.Str
 1005         """
 1006         self.__addNode(node)
 1007 
 1008     def visit_Bytes(self, node):
 1009         """
 1010         Public method to record a bytes node.
 1011         
 1012         @param node reference to the bytes node
 1013         @type ast.Bytes
 1014         """
 1015         self.__addNode(node)
 1016     
 1017     def visit_Constant(self, node):
 1018         """
 1019         Public method to handle constant nodes.
 1020         
 1021         @param node reference to the bytes node
 1022         @type ast.Constant
 1023         """
 1024         if sys.version_info >= (3, 8, 0):
 1025             if AstUtilities.isBaseString(node):
 1026                 self.__addNode(node)
 1027             else:
 1028                 super(TextVisitor, self).generic_visit(node)
 1029         else:
 1030             super(TextVisitor, self).generic_visit(node)
 1031 
 1032     def __visitDefinition(self, node):
 1033         """
 1034         Private method handling class and function definitions.
 1035         
 1036         @param node reference to the node to handle
 1037         @type ast.FunctionDef, ast.AsyncFunctionDef or ast.ClassDef
 1038         """
 1039         # Manually traverse class or function definition
 1040         # * Handle decorators normally
 1041         # * Use special check for body content
 1042         # * Don't handle the rest (e.g. bases)
 1043         for decorator in node.decorator_list:
 1044             self.visit(decorator)
 1045         self.__visitBody(node)
 1046 
 1047     def __visitBody(self, node):
 1048         """
 1049         Private method to traverse the body of the node manually.
 1050 
 1051         If the first node is an expression which contains a string or bytes it
 1052         marks that as a docstring.
 1053         
 1054         @param node reference to the node to traverse
 1055         @type ast.AST
 1056         """
 1057         if (
 1058             node.body and
 1059             isinstance(node.body[0], ast.Expr) and
 1060             AstUtilities.isBaseString(node.body[0].value)
 1061         ):
 1062             node.body[0].value.is_docstring = True
 1063 
 1064         for subnode in node.body:
 1065             self.visit(subnode)
 1066 
 1067     def visit_Module(self, node):
 1068         """
 1069         Public method to handle a module.
 1070         
 1071         @param node reference to the node to handle
 1072         @type ast.Module
 1073         """
 1074         self.__visitBody(node)
 1075 
 1076     def visit_ClassDef(self, node):
 1077         """
 1078         Public method to handle a class definition.
 1079         
 1080         @param node reference to the node to handle
 1081         @type ast.ClassDef
 1082         """
 1083         # Skipped nodes: ('name', 'bases', 'keywords', 'starargs', 'kwargs')
 1084         self.__visitDefinition(node)
 1085 
 1086     def visit_FunctionDef(self, node):
 1087         """
 1088         Public method to handle a function definition.
 1089         
 1090         @param node reference to the node to handle
 1091         @type ast.FunctionDef
 1092         """
 1093         # Skipped nodes: ('name', 'args', 'returns')
 1094         self.__visitDefinition(node)
 1095 
 1096     def visit_AsyncFunctionDef(self, node):
 1097         """
 1098         Public method to handle an asynchronous function definition.
 1099         
 1100         @param node reference to the node to handle
 1101         @type ast.AsyncFunctionDef
 1102         """
 1103         # Skipped nodes: ('name', 'args', 'returns')
 1104         self.__visitDefinition(node)
 1105 
 1106     def visit_Call(self, node):
 1107         """
 1108         Public method to handle a function call.
 1109         
 1110         @param node reference to the node to handle
 1111         @type ast.Call
 1112         """
 1113         if (
 1114             isinstance(node.func, ast.Attribute) and
 1115             node.func.attr == 'format'
 1116         ):
 1117             if AstUtilities.isBaseString(node.func.value):
 1118                 self.calls[node.func.value] = (node, False)
 1119             elif (
 1120                 isinstance(node.func.value, ast.Name) and
 1121                 node.func.value.id == 'str' and
 1122                 node.args and
 1123                 AstUtilities.isBaseString(node.args[0])
 1124             ):
 1125                 self.calls[node.args[0]] = (node, True)
 1126         super(TextVisitor, self).generic_visit(node)
 1127 
 1128 
 1129 class LoggingVisitor(ast.NodeVisitor):
 1130     """
 1131     Class implementing a node visitor to check logging statements.
 1132     """
 1133     LoggingLevels = {
 1134         "debug",
 1135         "critical",
 1136         "error",
 1137         "info",
 1138         "warn",
 1139         "warning",
 1140     }
 1141     
 1142     def __init__(self):
 1143         """
 1144         Constructor
 1145         """
 1146         super(LoggingVisitor, self).__init__()
 1147         
 1148         self.__currentLoggingCall = None
 1149         self.__currentLoggingArgument = None
 1150         self.__currentLoggingLevel = None
 1151         self.__currentExtraKeyword = None
 1152         self.violations = []
 1153 
 1154     def __withinLoggingStatement(self):
 1155         """
 1156         Private method to check, if we are inside a logging statement.
 1157         
 1158         @return flag indicating we are inside a logging statement
 1159         @rtype bool
 1160         """
 1161         return self.__currentLoggingCall is not None
 1162 
 1163     def __withinLoggingArgument(self):
 1164         """
 1165         Private method to check, if we are inside a logging argument.
 1166         
 1167         @return flag indicating we are inside a logging argument
 1168         @rtype bool
 1169         """
 1170         return self.__currentLoggingArgument is not None
 1171 
 1172     def __withinExtraKeyword(self, node):
 1173         """
 1174         Private method to check, if we are inside the extra keyword.
 1175         
 1176         @param node reference to the node to be checked
 1177         @type ast.keyword
 1178         @return flag indicating we are inside the extra keyword
 1179         @rtype bool
 1180         """
 1181         return (
 1182             self.__currentExtraKeyword is not None and
 1183             self.__currentExtraKeyword != node
 1184         )
 1185     
 1186     def __detectLoggingLevel(self, node):
 1187         """
 1188         Private method to decide whether an AST Call is a logging call.
 1189         
 1190         @param node reference to the node to be processed
 1191         @type ast.Call
 1192         @return logging level
 1193         @rtype str or None
 1194         """
 1195         try:
 1196             if node.func.value.id == "warnings":
 1197                 return None
 1198             
 1199             if node.func.attr in LoggingVisitor.LoggingLevels:
 1200                 return node.func.attr
 1201         except AttributeError:
 1202             pass
 1203         
 1204         return None
 1205 
 1206     def __isFormatCall(self, node):
 1207         """
 1208         Private method to check if a function call uses format.
 1209 
 1210         @param node reference to the node to be processed
 1211         @type ast.Call
 1212         @return flag indicating the function call uses format
 1213         @rtype bool
 1214         """
 1215         try:
 1216             return node.func.attr == "format"
 1217         except AttributeError:
 1218             return False
 1219     
 1220     def visit_Call(self, node):
 1221         """
 1222         Public method to handle a function call.
 1223 
 1224         Every logging statement and string format is expected to be a function
 1225         call.
 1226         
 1227         @param node reference to the node to be processed
 1228         @type ast.Call
 1229         """
 1230         # we are in a logging statement
 1231         if self.__withinLoggingStatement():
 1232             if self.__withinLoggingArgument() and self.__isFormatCall(node):
 1233                 self.violations.append((node, "M651"))
 1234                 super(LoggingVisitor, self).generic_visit(node)
 1235                 return
 1236         
 1237         loggingLevel = self.__detectLoggingLevel(node)
 1238         
 1239         if loggingLevel and self.__currentLoggingLevel is None:
 1240             self.__currentLoggingLevel = loggingLevel
 1241         
 1242         # we are in some other statement
 1243         if loggingLevel is None:
 1244             super(LoggingVisitor, self).generic_visit(node)
 1245             return
 1246         
 1247         # we are entering a new logging statement
 1248         self.__currentLoggingCall = node
 1249         
 1250         if loggingLevel == "warn":
 1251             self.violations.append((node, "M655"))
 1252         
 1253         for index, child in enumerate(ast.iter_child_nodes(node)):
 1254             if index == 1:
 1255                 self.__currentLoggingArgument = child
 1256             if (
 1257                 index > 1 and
 1258                 isinstance(child, ast.keyword) and
 1259                 child.arg == "extra"
 1260             ):
 1261                 self.__currentExtraKeyword = child
 1262             
 1263             super(LoggingVisitor, self).visit(child)
 1264             
 1265             self.__currentLoggingArgument = None
 1266             self.__currentExtraKeyword = None
 1267         
 1268         self.__currentLoggingCall = None
 1269         self.__currentLoggingLevel = None
 1270     
 1271     def visit_BinOp(self, node):
 1272         """
 1273         Public method to handle binary operations while processing the first
 1274         logging argument.
 1275         
 1276         @param node reference to the node to be processed
 1277         @type ast.BinOp
 1278         """
 1279         if self.__withinLoggingStatement() and self.__withinLoggingArgument():
 1280             # handle percent format
 1281             if isinstance(node.op, ast.Mod):
 1282                 self.violations.append((node, "M652"))
 1283             
 1284             # handle string concat
 1285             if isinstance(node.op, ast.Add):
 1286                 self.violations.append((node, "M653"))
 1287         
 1288         super(LoggingVisitor, self).generic_visit(node)
 1289     
 1290     def visit_JoinedStr(self, node):
 1291         """
 1292         Public method to handle f-string arguments.
 1293         
 1294         @param node reference to the node to be processed
 1295         @type ast.JoinedStr
 1296         """
 1297         if sys.version_info >= (3, 6):
 1298             if self.__withinLoggingStatement():
 1299                 if any(isinstance(i, ast.FormattedValue) for i in node.values):
 1300                     if self.__withinLoggingArgument():
 1301                         self.violations.append((node, "M654"))
 1302                         
 1303                         super(LoggingVisitor, self).generic_visit(node)
 1304 
 1305 
 1306 class BugBearVisitor(ast.NodeVisitor):
 1307     """
 1308     Class implementing a node visitor to check for various topics.
 1309     """
 1310     #
 1311     # This class was implemented along the BugBear flake8 extension (v 19.3.0).
 1312     # Original: Copyright (c) 2016 Ɓukasz Langa
 1313     #
 1314     
 1315     NodeWindowSize = 4
 1316     
 1317     def __init__(self):
 1318         """
 1319         Constructor
 1320         """
 1321         super(BugBearVisitor, self).__init__()
 1322         
 1323         self.__nodeStack = []
 1324         self.__nodeWindow = []
 1325         self.violations = []
 1326     
 1327     def visit(self, node):
 1328         """
 1329         Public method to traverse a given AST node.
 1330         
 1331         @param node AST node to be traversed
 1332         @type ast.Node
 1333         """
 1334         self.__nodeStack.append(node)
 1335         self.__nodeWindow.append(node)
 1336         self.__nodeWindow = self.__nodeWindow[-BugBearVisitor.NodeWindowSize:]
 1337         
 1338         super(BugBearVisitor, self).visit(node)
 1339         
 1340         self.__nodeStack.pop()
 1341     
 1342     def visit_UAdd(self, node):
 1343         """
 1344         Public method to handle unary additions.
 1345         
 1346         @param node reference to the node to be processed
 1347         @type ast.UAdd
 1348         """
 1349         trailingNodes = list(map(type, self.__nodeWindow[-4:]))
 1350         if trailingNodes == [ast.UnaryOp, ast.UAdd, ast.UnaryOp, ast.UAdd]:
 1351             originator = self.__nodeWindow[-4]
 1352             self.violations.append((originator, "M501"))
 1353         
 1354         self.generic_visit(node)
 1355     
 1356     def visit_Call(self, node):
 1357         """
 1358         Public method to handle a function call.
 1359         
 1360         @param node reference to the node to be processed
 1361         @type ast.Call
 1362         """
 1363         validPaths = ("six", "future.utils", "builtins")
 1364         methodsDict = {
 1365             "M521": ("iterkeys", "itervalues", "iteritems", "iterlists"),
 1366             "M522": ("viewkeys", "viewvalues", "viewitems", "viewlists"),
 1367             "M523": ("next",),
 1368         }
 1369         
 1370         if isinstance(node.func, ast.Attribute):
 1371             for code, methods in methodsDict.items():
 1372                 if node.func.attr in methods:
 1373                     callPath = ".".join(composeCallPath(node.func.value))
 1374                     if callPath not in validPaths:
 1375                         self.violations.append((node, code))
 1376                     break
 1377             else:
 1378                 self.__checkForM502(node)
 1379         else:
 1380             try:
 1381                 # bad super() call
 1382                 if isinstance(node.func, ast.Name) and node.func.id == "super":
 1383                     args = node.args
 1384                     if (
 1385                         len(args) == 2 and
 1386                         isinstance(args[0], ast.Attribute) and
 1387                         isinstance(args[0].value, ast.Name) and
 1388                         args[0].value.id == 'self' and
 1389                         args[0].attr == '__class__'
 1390                     ):
 1391                         self.violations.append((node, "M509"))
 1392                 
 1393                 # bad getattr and setattr
 1394                 if (
 1395                     node.func.id in ("getattr", "hasattr") and
 1396                     node.args[1].s == "__call__"
 1397                 ):
 1398                     self.violations.append((node, "M511"))
 1399                 if (
 1400                     node.func.id == "getattr" and
 1401                     len(node.args) == 2 and
 1402                     AstUtilities.isString(node.args[1])
 1403                 ):
 1404                     self.violations.append((node, "M512"))
 1405                 elif (
 1406                     node.func.id == "setattr" and
 1407                     len(node.args) == 3 and
 1408                     AstUtilities.isString(node.args[1])
 1409                 ):
 1410                     self.violations.append((node, "M513"))
 1411             except (AttributeError, IndexError):
 1412                 pass
 1413 
 1414             self.generic_visit(node)
 1415     
 1416     def visit_Attribute(self, node):
 1417         """
 1418         Public method to handle attributes.
 1419         
 1420         @param node reference to the node to be processed
 1421         @type ast.Attribute
 1422         """
 1423         callPath = list(composeCallPath(node))
 1424         
 1425         if '.'.join(callPath) == 'sys.maxint':
 1426             self.violations.append((node, "M504"))
 1427         
 1428         elif (
 1429             len(callPath) == 2 and
 1430             callPath[1] == 'message'
 1431         ):
 1432             name = callPath[0]
 1433             for elem in reversed(self.__nodeStack[:-1]):
 1434                 if isinstance(elem, ast.ExceptHandler) and elem.name == name:
 1435                     self.violations.append((node, "M505"))
 1436                     break
 1437     
 1438     def visit_Assign(self, node):
 1439         """
 1440         Public method to handle assignments.
 1441         
 1442         @param node reference to the node to be processed
 1443         @type ast.Assign
 1444         """
 1445         if isinstance(self.__nodeStack[-2], ast.ClassDef):
 1446             # By using 'hasattr' below we're ignoring starred arguments, slices
 1447             # and tuples for simplicity.
 1448             assignTargets = {t.id for t in node.targets if hasattr(t, 'id')}
 1449             if '__metaclass__' in assignTargets:
 1450                 self.violations.append((node, "M524"))
 1451         
 1452         elif len(node.targets) == 1:
 1453             target = node.targets[0]
 1454             if (
 1455                 isinstance(target, ast.Attribute) and
 1456                 isinstance(target.value, ast.Name)
 1457             ):
 1458                 if (target.value.id, target.attr) == ('os', 'environ'):
 1459                     self.violations.append((node, "M506"))
 1460         
 1461         self.generic_visit(node)
 1462     
 1463     def visit_For(self, node):
 1464         """
 1465         Public method to handle 'for' statements.
 1466         
 1467         @param node reference to the node to be processed
 1468         @type ast.For
 1469         """
 1470         self.__checkForM507(node)
 1471         
 1472         self.generic_visit(node)
 1473     
 1474     def visit_AsyncFor(self, node):
 1475         """
 1476         Public method to handle 'for' statements.
 1477         
 1478         @param node reference to the node to be processed
 1479         @type ast.AsyncFor
 1480         """
 1481         self.__checkForM507(node)
 1482         
 1483         self.generic_visit(node)
 1484     
 1485     def visit_Assert(self, node):
 1486         """
 1487         Public method to handle 'assert' statements.
 1488         
 1489         @param node reference to the node to be processed
 1490         @type ast.Assert
 1491         """
 1492         if (
 1493             AstUtilities.isNameConstant(node.test) and
 1494             AstUtilities.getValue(node.test) is False
 1495         ):
 1496             self.violations.append((node, "M503"))
 1497         
 1498         self.generic_visit(node)
 1499     
 1500     def visit_JoinedStr(self, node):
 1501         """
 1502         Public method to handle f-string arguments.
 1503         
 1504         @param node reference to the node to be processed
 1505         @type ast.JoinedStr
 1506         """
 1507         if sys.version_info >= (3, 6):
 1508             for value in node.values:
 1509                 if isinstance(value, ast.FormattedValue):
 1510                     return
 1511             
 1512             self.violations.append((node, "M508"))
 1513     
 1514     def __checkForM502(self, node):
 1515         """
 1516         Private method to check the use of *strip().
 1517         
 1518         @param node reference to the node to be processed
 1519         @type ast.Call
 1520         """
 1521         if node.func.attr not in ("lstrip", "rstrip", "strip"):
 1522             return          # method name doesn't match
 1523         
 1524         if len(node.args) != 1 or not AstUtilities.isString(node.args[0]):
 1525             return          # used arguments don't match the builtin strip
 1526         
 1527         s = AstUtilities.getValue(node.args[0])
 1528         if len(s) == 1:
 1529             return          # stripping just one character
 1530         
 1531         if len(s) == len(set(s)):
 1532             return          # no characters appear more than once
 1533 
 1534         self.violations.append((node, "M502"))
 1535     
 1536     def __checkForM507(self, node):
 1537         """
 1538         Private method to check for unused loop variables.
 1539         
 1540         @param node reference to the node to be processed
 1541         @type ast.For
 1542         """
 1543         targets = NameFinder()
 1544         targets.visit(node.target)
 1545         ctrlNames = set(filter(lambda s: not s.startswith('_'),
 1546                                targets.getNames()))
 1547         body = NameFinder()
 1548         for expr in node.body:
 1549             body.visit(expr)
 1550         usedNames = set(body.getNames())
 1551         for name in sorted(ctrlNames - usedNames):
 1552             n = targets.getNames()[name][0]
 1553             self.violations.append((n, "M507", name))
 1554 
 1555 
 1556 class NameFinder(ast.NodeVisitor):
 1557     """
 1558     Class to extract a name out of a tree of nodes.
 1559     """
 1560     def __init__(self):
 1561         """
 1562         Constructor
 1563         """
 1564         super(NameFinder, self).__init__()
 1565         
 1566         self.__names = {}
 1567 
 1568     def visit_Name(self, node):
 1569         """
 1570         Public method to handle 'Name' nodes.
 1571         
 1572         @param node reference to the node to be processed
 1573         @type ast.Name
 1574         """
 1575         self.__names.setdefault(node.id, []).append(node)
 1576 
 1577     def visit(self, node):
 1578         """
 1579         Public method to traverse a given AST node.
 1580         
 1581         @param node AST node to be traversed
 1582         @type ast.Node
 1583         """
 1584         if isinstance(node, list):
 1585             for elem in node:
 1586                 super(NameFinder, self).visit(elem)
 1587         else:
 1588             super(NameFinder, self).visit(node)
 1589     
 1590     def getNames(self):
 1591         """
 1592         Public method to return the extracted names and Name nodes.
 1593         
 1594         @return dictionary containing the names as keys and the list of nodes
 1595         @rtype dict
 1596         """
 1597         return self.__names
 1598 
 1599 
 1600 class ReturnVisitor(ast.NodeVisitor):
 1601     """
 1602     Class implementing a node visitor to check return statements.
 1603     """
 1604     Assigns = 'assigns'
 1605     Refs = 'refs'
 1606     Returns = 'returns'
 1607     
 1608     def __init__(self):
 1609         """
 1610         Constructor
 1611         """
 1612         super(ReturnVisitor, self).__init__()
 1613         
 1614         self.__stack = []
 1615         self.violations = []
 1616         self.__loopCount = 0
 1617     
 1618     @property
 1619     def assigns(self):
 1620         """
 1621         Public method to get the Assign nodes.
 1622         
 1623         @return dictionary containing the node name as key and line number
 1624             as value
 1625         @rtype dict
 1626         """
 1627         return self.__stack[-1][ReturnVisitor.Assigns]
 1628     
 1629     @property
 1630     def refs(self):
 1631         """
 1632         Public method to get the References nodes.
 1633         
 1634         @return dictionary containing the node name as key and line number
 1635             as value
 1636         @rtype dict
 1637         """
 1638         return self.__stack[-1][ReturnVisitor.Refs]
 1639     
 1640     @property
 1641     def returns(self):
 1642         """
 1643         Public method to get the Return nodes.
 1644         
 1645         @return dictionary containing the node name as key and line number
 1646             as value
 1647         @rtype dict
 1648         """
 1649         return self.__stack[-1][ReturnVisitor.Returns]
 1650     
 1651     def visit_For(self, node):
 1652         """
 1653         Public method to handle a for loop.
 1654         
 1655         @param node reference to the for node to handle
 1656         @type ast.For
 1657         """
 1658         self.__visitLoop(node)
 1659     
 1660     def visit_AsyncFor(self, node):
 1661         """
 1662         Public method to handle an async for loop.
 1663         
 1664         @param node reference to the async for node to handle
 1665         @type ast.AsyncFor
 1666         """
 1667         self.__visitLoop(node)
 1668     
 1669     def visit_While(self, node):
 1670         """
 1671         Public method to handle a while loop.
 1672         
 1673         @param node reference to the while node to handle
 1674         @type ast.While
 1675         """
 1676         self.__visitLoop(node)
 1677     
 1678     def __visitLoop(self, node):
 1679         """
 1680         Private method to handle loop nodes.
 1681         
 1682         @param node reference to the loop node to handle
 1683         @type ast.For, ast.AsyncFor or ast.While
 1684         """
 1685         self.__loopCount += 1
 1686         self.generic_visit(node)
 1687         self.__loopCount -= 1
 1688     
 1689     def __visitWithStack(self, node):
 1690         """
 1691         Private method to traverse a given function node using a stack.
 1692         
 1693         @param node AST node to be traversed
 1694         @type ast.FunctionDef or ast.AsyncFunctionDef
 1695         """
 1696         self.__stack.append({
 1697             ReturnVisitor.Assigns: defaultdict(list),
 1698             ReturnVisitor.Refs: defaultdict(list),
 1699             ReturnVisitor.Returns: []
 1700         })
 1701         
 1702         self.generic_visit(node)
 1703         self.__checkFunction(node)
 1704         self.__stack.pop()
 1705     
 1706     def visit_FunctionDef(self, node):
 1707         """
 1708         Public method to handle a function definition.
 1709         
 1710         @param node reference to the node to handle
 1711         @type ast.FunctionDef
 1712         """
 1713         self.__visitWithStack(node)
 1714     
 1715     def visit_AsyncFunctionDef(self, node):
 1716         """
 1717         Public method to handle a function definition.
 1718         
 1719         @param node reference to the node to handle
 1720         @type ast.AsyncFunctionDef
 1721         """
 1722         self.__visitWithStack(node)
 1723     
 1724     def visit_Return(self, node):
 1725         """
 1726         Public method to handle a return node.
 1727         
 1728         @param node reference to the node to handle
 1729         @type ast.Return
 1730         """
 1731         self.returns.append(node)
 1732         self.generic_visit(node)
 1733     
 1734     def visit_Assign(self, node):
 1735         """
 1736         Public method to handle an assign node.
 1737         
 1738         @param node reference to the node to handle
 1739         @type ast.Assign
 1740         """
 1741         if not self.__stack:
 1742             return
 1743 
 1744         self.generic_visit(node.value)
 1745         
 1746         target = node.targets[0]
 1747         if (
 1748             isinstance(target, ast.Tuple) and
 1749             not isinstance(node.value, ast.Tuple)
 1750         ):
 1751             # skip unpacking assign
 1752             return
 1753         
 1754         self.__visitAssignTarget(target)
 1755     
 1756     def visit_Name(self, node):
 1757         """
 1758         Public method to handle a name node.
 1759         
 1760         @param node reference to the node to handle
 1761         @type ast.Name
 1762         """
 1763         if self.__stack:
 1764             self.refs[node.id].append(node.lineno)
 1765     
 1766     def __visitAssignTarget(self, node):
 1767         """
 1768         Private method to handle an assign target node.
 1769         
 1770         @param node reference to the node to handle
 1771         @type ast.AST
 1772         """
 1773         if isinstance(node, ast.Tuple):
 1774             for elt in node.elts:
 1775                 self.__visitAssignTarget(elt)
 1776             return
 1777         
 1778         if not self.__loopCount and isinstance(node, ast.Name):
 1779             self.assigns[node.id].append(node.lineno)
 1780             return
 1781         
 1782         self.generic_visit(node)
 1783     
 1784     def __checkFunction(self, node):
 1785         """
 1786         Private method to check a function definition node.
 1787         
 1788         @param node reference to the node to check
 1789         @type ast.AsyncFunctionDef or ast.FunctionDef
 1790         """
 1791         if not self.returns or not node.body:
 1792             return
 1793         
 1794         if len(node.body) == 1 and isinstance(node.body[-1], ast.Return):
 1795             # skip functions that consist of `return None` only
 1796             return
 1797         
 1798         if not self.__resultExists():
 1799             self.__checkUnnecessaryReturnNone()
 1800             return
 1801         
 1802         self.__checkImplicitReturnValue()
 1803         self.__checkImplicitReturn(node.body[-1])
 1804         
 1805         for n in self.returns:
 1806             if n.value:
 1807                 self.__checkUnnecessaryAssign(n.value)
 1808     
 1809     def __isNone(self, node):
 1810         """
 1811         Private method to check, if a node value is None.
 1812         
 1813         @param node reference to the node to check
 1814         @type ast.AST
 1815         @return flag indicating the node contains a None value
 1816         @rtype bool
 1817         """
 1818         return (
 1819             AstUtilities.isNameConstant(node) and
 1820             AstUtilities.getValue(node) is None
 1821         )
 1822     
 1823     def __isFalse(self, node):
 1824         """
 1825         Private method to check, if a node value is False.
 1826         
 1827         @param node reference to the node to check
 1828         @type ast.AST
 1829         @return flag indicating the node contains a False value
 1830         @rtype bool
 1831         """
 1832         return (
 1833             AstUtilities.isNameConstant(node) and
 1834             AstUtilities.getValue(node) is False
 1835         )
 1836     
 1837     def __resultExists(self):
 1838         """
 1839         Private method to check the existance of a return result.
 1840         
 1841         @return flag indicating the existence of a return result
 1842         @rtype bool
 1843         """
 1844         for node in self.returns:
 1845             value = node.value
 1846             if value and not self.__isNone(value):
 1847                 return True
 1848         
 1849         return False
 1850     
 1851     def __checkImplicitReturnValue(self):
 1852         """
 1853         Private method to check for implicit return values.
 1854         """
 1855         for node in self.returns:
 1856             if not node.value:
 1857                 self.violations.append((node, "M832"))
 1858     
 1859     def __checkUnnecessaryReturnNone(self):
 1860         """
 1861         Private method to check for an unnecessary 'return None' statement.
 1862         """
 1863         for node in self.returns:
 1864             if self.__isNone(node.value):
 1865                 self.violations.append((node, "M831"))
 1866     
 1867     def __checkImplicitReturn(self, node):
 1868         """
 1869         Private method to check for an implicit return statement.
 1870         
 1871         @param node reference to the node to check
 1872         @type ast.AST
 1873         """
 1874         if isinstance(node, ast.If):
 1875             if not node.body or not node.orelse:
 1876                 self.violations.append((node, "M833"))
 1877                 return
 1878             
 1879             self.__checkImplicitReturn(node.body[-1])
 1880             self.__checkImplicitReturn(node.orelse[-1])
 1881             return
 1882         
 1883         if isinstance(node, (ast.For, ast.AsyncFor)) and node.orelse:
 1884             self.__checkImplicitReturn(node.orelse[-1])
 1885             return
 1886         
 1887         if isinstance(node, (ast.With, ast.AsyncWith)):
 1888             self.__checkImplicitReturn(node.body[-1])
 1889             return
 1890         
 1891         if isinstance(node, ast.Assert) and self.__isFalse(node.test):
 1892             return
 1893         
 1894         try:
 1895             okNodes = (ast.Return, ast.Raise, ast.While, ast.Try)
 1896         except AttributeError:
 1897             okNodes = (ast.Return, ast.Raise, ast.While)
 1898         if not isinstance(node, okNodes):
 1899             self.violations.append((node, "M833"))
 1900     
 1901     def __checkUnnecessaryAssign(self, node):
 1902         """
 1903         Private method to check for an unnecessary assign statement.
 1904         
 1905         @param node reference to the node to check
 1906         @type ast.AST
 1907         """
 1908         if not isinstance(node, ast.Name):
 1909             return
 1910         
 1911         varname = node.id
 1912         returnLineno = node.lineno
 1913         
 1914         if varname not in self.assigns:
 1915             return
 1916         
 1917         if varname not in self.refs:
 1918             self.violations.append((node, "M834"))
 1919             return
 1920         
 1921         if self.__hasRefsBeforeNextAssign(varname, returnLineno):
 1922             return
 1923         
 1924         self.violations.append((node, "M834"))
 1925 
 1926     def __hasRefsBeforeNextAssign(self, varname, returnLineno):
 1927         """
 1928         Private method to check for references before a following assign
 1929         statement.
 1930         
 1931         @param varname variable name to check for
 1932         @type str
 1933         @param returnLineno line number of the return statement
 1934         @type int
 1935         @return flag indicating the existence of references
 1936         @rtype bool
 1937         """
 1938         beforeAssign = 0
 1939         afterAssign = None
 1940         
 1941         for lineno in sorted(self.assigns[varname]):
 1942             if lineno > returnLineno:
 1943                 afterAssign = lineno
 1944                 break
 1945             
 1946             if lineno <= returnLineno:
 1947                 beforeAssign = lineno
 1948         
 1949         for lineno in self.refs[varname]:
 1950             if lineno == returnLineno:
 1951                 continue
 1952             
 1953             if afterAssign:
 1954                 if beforeAssign < lineno <= afterAssign:
 1955                     return True
 1956             
 1957             elif beforeAssign < lineno:
 1958                 return True
 1959         
 1960         return False
 1961 
 1962 
 1963 class DateTimeVisitor(ast.NodeVisitor):
 1964     """
 1965     Class implementing a node visitor to check datetime function calls.
 1966     
 1967     Note: This class is modelled after flake8_datetimez checker.
 1968     """
 1969     def __init__(self):
 1970         """
 1971         Constructor
 1972         """
 1973         super(DateTimeVisitor, self).__init__()
 1974         
 1975         self.violations = []
 1976     
 1977     def __getFromKeywords(self, keywords, name):
 1978         """
 1979         Private method to get a keyword node given its name.
 1980         
 1981         @param keywords list of keyword argument nodes
 1982         @type list of ast.AST
 1983         @param name name of the keyword node
 1984         @type str
 1985         @return keyword node
 1986         @rtype ast.AST
 1987         """
 1988         for keyword in keywords:
 1989             if keyword.arg == name:
 1990                 return keyword
 1991         
 1992         return None
 1993     
 1994     def visit_Call(self, node):
 1995         """
 1996         Public method to handle a function call.
 1997 
 1998         Every datetime related function call is check for use of the naive
 1999         variant (i.e. use without TZ info).
 2000         
 2001         @param node reference to the node to be processed
 2002         @type ast.Call
 2003         """
 2004         # datetime.something()
 2005         isDateTimeClass = (
 2006             isinstance(node.func, ast.Attribute) and
 2007             isinstance(node.func.value, ast.Name) and
 2008             node.func.value.id == 'datetime')
 2009         
 2010         # datetime.datetime.something()
 2011         isDateTimeModuleAndClass = (
 2012             isinstance(node.func, ast.Attribute) and
 2013             isinstance(node.func.value, ast.Attribute) and
 2014             node.func.value.attr == 'datetime' and
 2015             isinstance(node.func.value.value, ast.Name) and
 2016             node.func.value.value.id == 'datetime')
 2017         
 2018         if isDateTimeClass:
 2019             if node.func.attr == 'datetime':
 2020                 # datetime.datetime(2000, 1, 1, 0, 0, 0, 0,
 2021                 #                   datetime.timezone.utc)
 2022                 isCase1 = (
 2023                     len(node.args) >= 8 and
 2024                     not (
 2025                         AstUtilities.isNameConstant(node.args[7]) and
 2026                         AstUtilities.getValue(node.args[7]) is None
 2027                     )
 2028                 )
 2029                 
 2030                 # datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
 2031                 tzinfoKeyword = self.__getFromKeywords(node.keywords, 'tzinfo')
 2032                 isCase2 = (
 2033                     tzinfoKeyword is not None and
 2034                     not (
 2035                         AstUtilities.isNameConstant(tzinfoKeyword.value) and
 2036                         AstUtilities.getValue(tzinfoKeyword.value) is None
 2037                     )
 2038                 )
 2039                 
 2040                 if not (isCase1 or isCase2):
 2041                     self.violations.append((node, "M301"))
 2042             
 2043             elif node.func.attr == 'time':
 2044                 # time(12, 10, 45, 0, datetime.timezone.utc)
 2045                 isCase1 = (
 2046                     len(node.args) >= 5 and
 2047                     not (
 2048                         AstUtilities.isNameConstant(node.args[4]) and
 2049                         AstUtilities.getValue(node.args[4]) is None
 2050                     )
 2051                 )
 2052                 
 2053                 # datetime.time(12, 10, 45, tzinfo=datetime.timezone.utc)
 2054                 tzinfoKeyword = self.__getFromKeywords(node.keywords, 'tzinfo')
 2055                 isCase2 = (
 2056                     tzinfoKeyword is not None and
 2057                     not (
 2058                         AstUtilities.isNameConstant(tzinfoKeyword.value) and
 2059                         AstUtilities.getValue(tzinfoKeyword.value) is None
 2060                     )
 2061                 )
 2062                 
 2063                 if not (isCase1 or isCase2):
 2064                     self.violations.append((node, "M321"))
 2065             
 2066             elif node.func.attr == 'date':
 2067                 self.violations.append((node, "M311"))
 2068         
 2069         if isDateTimeClass or isDateTimeModuleAndClass:
 2070             if node.func.attr == 'today':
 2071                 self.violations.append((node, "M302"))
 2072             
 2073             elif node.func.attr == 'utcnow':
 2074                 self.violations.append((node, "M303"))
 2075             
 2076             elif node.func.attr == 'utcfromtimestamp':
 2077                 self.violations.append((node, "M304"))
 2078             
 2079             elif node.func.attr in 'now':
 2080                 # datetime.now(UTC)
 2081                 isCase1 = (
 2082                     len(node.args) == 1 and
 2083                     len(node.keywords) == 0 and
 2084                     not (
 2085                         AstUtilities.isNameConstant(node.args[0]) and
 2086                         AstUtilities.getValue(node.args[0]) is None
 2087                     )
 2088                 )
 2089                 
 2090                 # datetime.now(tz=UTC)
 2091                 tzKeyword = self.__getFromKeywords(node.keywords, 'tz')
 2092                 isCase2 = (
 2093                     tzKeyword is not None and
 2094                     not (
 2095                         AstUtilities.isNameConstant(tzKeyword.value) and
 2096                         AstUtilities.getValue(tzKeyword.value) is None
 2097                     )
 2098                 )
 2099                 
 2100                 if not (isCase1 or isCase2):
 2101                     self.violations.append((node, "M305"))
 2102             
 2103             elif node.func.attr == 'fromtimestamp':
 2104                 # datetime.fromtimestamp(1234, UTC)
 2105                 isCase1 = (
 2106                     len(node.args) == 2 and
 2107                     len(node.keywords) == 0 and
 2108                     not (
 2109                         AstUtilities.isNameConstant(node.args[1]) and
 2110                         AstUtilities.getValue(node.args[1]) is None
 2111                     )
 2112                 )
 2113                 
 2114                 # datetime.fromtimestamp(1234, tz=UTC)
 2115                 tzKeyword = self.__getFromKeywords(node.keywords, 'tz')
 2116                 isCase2 = (
 2117                     tzKeyword is not None and
 2118                     not (
 2119                         AstUtilities.isNameConstant(tzKeyword.value) and
 2120                         AstUtilities.getValue(tzKeyword.value) is None
 2121                     )
 2122                 )
 2123                 
 2124                 if not (isCase1 or isCase2):
 2125                     self.violations.append((node, "M306"))
 2126             
 2127             elif node.func.attr == 'strptime':
 2128                 # datetime.strptime(...).replace(tzinfo=UTC)
 2129                 parent = getattr(node, '_dtCheckerParent', None)
 2130                 pparent = getattr(parent, '_dtCheckerParent', None)
 2131                 if not (isinstance(parent, ast.Attribute) and
 2132                         parent.attr == 'replace'):
 2133                     isCase1 = False
 2134                 elif not isinstance(pparent, ast.Call):
 2135                     isCase1 = False
 2136                 else:
 2137                     tzinfoKeyword = self.__getFromKeywords(pparent.keywords,
 2138                                                            'tzinfo')
 2139                     isCase1 = (
 2140                         tzinfoKeyword is not None and
 2141                         not (
 2142                             AstUtilities.isNameConstant(
 2143                                 tzinfoKeyword.value) and
 2144                             AstUtilities.getValue(tzinfoKeyword.value) is None
 2145                         )
 2146                     )
 2147                 
 2148                 if not isCase1:
 2149                     self.violations.append((node, "M307"))
 2150             
 2151             elif node.func.attr == 'fromordinal':
 2152                 self.violations.append((node, "M308"))
 2153         
 2154         # date.something()
 2155         isDateClass = (isinstance(node.func, ast.Attribute) and
 2156                        isinstance(node.func.value, ast.Name) and
 2157                        node.func.value.id == 'date')
 2158         
 2159         # datetime.date.something()
 2160         isDateModuleAndClass = (isinstance(node.func, ast.Attribute) and
 2161                                 isinstance(node.func.value, ast.Attribute) and
 2162                                 node.func.value.attr == 'date' and
 2163                                 isinstance(node.func.value.value, ast.Name) and
 2164                                 node.func.value.value.id == 'datetime')
 2165         
 2166         if isDateClass or isDateModuleAndClass:
 2167             if node.func.attr == 'today':
 2168                 self.violations.append((node, "M312"))
 2169             
 2170             elif node.func.attr == 'fromtimestamp':
 2171                 self.violations.append((node, "M313"))
 2172             
 2173             elif node.func.attr == 'fromordinal':
 2174                 self.violations.append((node, "M314"))
 2175             
 2176             elif node.func.attr == 'fromisoformat':
 2177                 self.violations.append((node, "M315"))
 2178         
 2179         self.generic_visit(node)
 2180 
 2181 
 2182 class SysVersionVisitor(ast.NodeVisitor):
 2183     """
 2184     Class implementing a node visitor to check the use of sys.version and
 2185     sys.version_info.
 2186     
 2187     Note: This class is modelled after flake8-2020 checker.
 2188     """
 2189     def __init__(self):
 2190         """
 2191         Constructor
 2192         """
 2193         super(SysVersionVisitor, self).__init__()
 2194         
 2195         self.violations = []
 2196         self.__fromImports = {}
 2197     
 2198     def visit_ImportFrom(self, node):
 2199         """
 2200         Public method to handle a from ... import ... statement.
 2201         
 2202         @param node reference to the node to be processed
 2203         @type ast.ImportFrom
 2204         """
 2205         for alias in node.names:
 2206             if node.module is not None and not alias.asname:
 2207                 self.__fromImports[alias.name] = node.module
 2208         
 2209         self.generic_visit(node)
 2210     
 2211     def __isSys(self, attr, node):
 2212         """
 2213         Private method to check for a reference to sys attribute.
 2214         
 2215         @param attr attribute name
 2216         @type str
 2217         @param node reference to the node to be checked
 2218         @type ast.Node
 2219         @return flag indicating a match
 2220         @rtype bool
 2221         """
 2222         match = False
 2223         if (
 2224             isinstance(node, ast.Attribute) and
 2225             isinstance(node.value, ast.Name) and
 2226             node.value.id == "sys" and
 2227             node.attr == attr
 2228         ):
 2229             match = True
 2230         elif (
 2231             isinstance(node, ast.Name) and
 2232             node.id == attr and
 2233             self.__fromImports.get(node.id) == "sys"
 2234         ):
 2235             match = True
 2236         
 2237         return match
 2238     
 2239     def __isSysVersionUpperSlice(self, node, n):
 2240         """
 2241         Private method to check the upper slice of sys.version.
 2242         
 2243         @param node reference to the node to be checked
 2244         @type ast.Node
 2245         @param n slice value to check against
 2246         @type int
 2247         @return flag indicating a match
 2248         @rtype bool
 2249         """
 2250         return (
 2251             self.__isSys("version", node.value) and
 2252             isinstance(node.slice, ast.Slice) and
 2253             node.slice.lower is None and
 2254             AstUtilities.isNumber(node.slice.upper) and
 2255             AstUtilities.getValue(node.slice.upper) == n and
 2256             node.slice.step is None
 2257         )
 2258     
 2259     def visit_Subscript(self, node):
 2260         """
 2261         Public method to handle a subscript.
 2262         
 2263         @param node reference to the node to be processed
 2264         @type ast.Subscript
 2265         """
 2266         if self.__isSysVersionUpperSlice(node, 1):
 2267             self.violations.append((node.value, "M423"))
 2268         elif self.__isSysVersionUpperSlice(node, 3):
 2269             self.violations.append((node.value, "M401"))
 2270         elif (
 2271             self.__isSys('version', node.value) and
 2272             isinstance(node.slice, ast.Index) and
 2273             AstUtilities.isNumber(node.slice.value) and
 2274             AstUtilities.getValue(node.slice.value) == 2
 2275         ):
 2276             self.violations.append((node.value, "M402"))
 2277         elif (
 2278             self.__isSys('version', node.value) and
 2279             isinstance(node.slice, ast.Index) and
 2280             AstUtilities.isNumber(node.slice.value) and
 2281             AstUtilities.getValue(node.slice.value) == 0
 2282         ):
 2283             self.violations.append((node.value, "M421"))
 2284 
 2285         self.generic_visit(node)
 2286     
 2287     def visit_Compare(self, node):
 2288         """
 2289         Public method to handle a comparison.
 2290         
 2291         @param node reference to the node to be processed
 2292         @type ast.Compare
 2293         """
 2294         if (
 2295             isinstance(node.left, ast.Subscript) and
 2296             self.__isSys('version_info', node.left.value) and
 2297             isinstance(node.left.slice, ast.Index) and
 2298             AstUtilities.isNumber(node.left.slice.value) and
 2299             AstUtilities.getValue(node.left.slice.value) == 0 and
 2300             len(node.ops) == 1 and
 2301             isinstance(node.ops[0], ast.Eq) and
 2302             AstUtilities.isNumber(node.comparators[0]) and
 2303             AstUtilities.getValue(node.comparators[0]) == 3
 2304         ):
 2305             self.violations.append((node.left, "M411"))
 2306         elif (
 2307             self.__isSys('version', node.left) and
 2308             len(node.ops) == 1 and
 2309             isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE)) and
 2310             AstUtilities.isString(node.comparators[0])
 2311         ):
 2312             if len(AstUtilities.getValue(node.comparators[0])) == 1:
 2313                 errorCode = "M422"
 2314             else:
 2315                 errorCode = "M403"
 2316             self.violations.append((node.left, errorCode))
 2317         elif (
 2318             isinstance(node.left, ast.Subscript) and
 2319             self.__isSys('version_info', node.left.value) and
 2320             isinstance(node.left.slice, ast.Index) and
 2321             AstUtilities.isNumber(node.left.slice.value) and
 2322             AstUtilities.getValue(node.left.slice.value) == 1 and
 2323             len(node.ops) == 1 and
 2324             isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE)) and
 2325             AstUtilities.isNumber(node.comparators[0])
 2326         ):
 2327             self.violations.append((node, "M413"))
 2328         elif (
 2329             isinstance(node.left, ast.Attribute) and
 2330             self.__isSys('version_info', node.left.value) and
 2331             node.left.attr == 'minor' and
 2332             len(node.ops) == 1 and
 2333             isinstance(node.ops[0], (ast.Lt, ast.LtE, ast.Gt, ast.GtE)) and
 2334             AstUtilities.isNumber(node.comparators[0])
 2335         ):
 2336             self.violations.append((node, "M414"))
 2337         
 2338         self.generic_visit(node)
 2339     
 2340     def visit_Attribute(self, node):
 2341         """
 2342         Public method to handle an attribute.
 2343         
 2344         @param node reference to the node to be processed
 2345         @type ast.Attribute
 2346         """
 2347         if (
 2348             isinstance(node.value, ast.Name) and
 2349             node.value.id == 'six' and
 2350             node.attr == 'PY3'
 2351         ):
 2352             self.violations.append((node, "M412"))
 2353         
 2354         self.generic_visit(node)
 2355 
 2356     def visit_Name(self, node):
 2357         """
 2358         Public method to handle an name.
 2359         
 2360         @param node reference to the node to be processed
 2361         @type ast.Name
 2362         """
 2363         if node.id == 'PY3' and self.__fromImports.get(node.id) == 'six':
 2364             self.violations.append((node, "M412"))
 2365         
 2366         self.generic_visit(node)
 2367 
 2368 #
 2369 # eflag: noqa = M891