src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Unused/UnusedChecker.py

branch
eric7
changeset 10053
9914b7b4b11c
parent 10052
041d0785dd42
child 10054
d7a47f0cff2b
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Unused/UnusedChecker.py	Mon May 22 19:53:41 2023 +0200
+++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Unused/UnusedChecker.py	Tue May 23 12:00:37 2023 +0200
@@ -8,6 +8,7 @@
 """
 
 import ast
+import collections
 import copy
 
 import AstUtilities
@@ -22,6 +23,8 @@
         ## Unused Arguments
         "U100",
         "U101",
+        ## Unused Globals
+        "U200",
     ]
 
     def __init__(self, source, filename, tree, select, ignore, expected, repeat, args):
@@ -54,16 +57,6 @@
         self.__tree = copy.deepcopy(tree)
         self.__args = args
 
-        ### parameters for unused arguments checks
-        ##self.__ignoreAbstract        "IgnoreAbstract": False,
-        ##self.__ignoreOverload        "IgnoreOverload": False,
-        ##self.__ignoreOverride        "IgnoreOverride": False,
-        ##self.__ignoreStubs        "IgnoreStubs": False,
-        ##self.__ignoreVariadicNames        "IgnoreVariadicNames": False,
-        ##self.__ignoreLambdas        "IgnoreLambdas": False,
-        ##self.__ignoreNestedFunctions        "IgnoreNestedFunctions": False,
-        ##self.__ignoreDunderMethods        "IgnoreDunderMethods": False,
-
         # statistics counters
         self.counters = {}
 
@@ -72,6 +65,7 @@
 
         checkersWithCodes = [
             (self.__checkUnusedArguments, ("U100", "U101")),
+            (self.__checkUnusedGlobals, ("U200", )),
         ]
 
         self.__checkers = []
@@ -364,6 +358,79 @@
 
         return orderedArguments
 
+    #######################################################################
+    ## Unused Arguments
+    ##
+    ## adapted from: flake8-unused-arguments v0.0.13
+    #######################################################################
+
+    def __checkUnusedGlobals(self):
+        """
+        Private method to check for unused global variables.
+        """
+        errors = {}
+        loadCounter = GlobalVariableLoadCounter()
+        loadCounter.visit(self.__tree)
+
+        globalVariables = self.__extractGlobalVariables()
+
+        for varId, loads in loadCounter.getLoads():
+            if varId in globalVariables and loads == 0:
+                storeInfo = loadCounter.getStoreInfo(varId)
+                errorInfo = (storeInfo.lineno - 1, storeInfo.offset, "U200", varId)
+                errors[varId] = errorInfo
+
+        for node in self.__tree.body[::-1]:
+            if isinstance(node, ast.Assign):
+                for target in node.targets:
+                    if isinstance(target, ast.Name) and target.id in errors:
+                        errors.pop(target.id)
+            elif (
+                isinstance(node, ast.AnnAssign)
+                and isinstance(node.target, ast.Name)
+                and node.target.id in errors
+            ):
+                errors.pop(node.target.id)
+            else:
+                break
+
+        if self.__args["IgnoreDunderGlobals"]:
+            # eliminate some special cases
+            for name in list(errors.keys()):
+                if name.startswith("__") and name.endswith("__"):
+                    errors.pop(name)
+
+        for varId in errors:
+            self.__error(*errors[varId])
+
+    def __extractGlobalVariables(self):
+        """
+        Private method to get the names of all global variables.
+
+        @return set containing the defined global variable names
+        @rtype set of str
+        """
+        variables = set()
+
+        for assignment in self.__tree.body:
+            if isinstance(assignment, ast.Assign):
+                for target in assignment.targets:
+                    if isinstance(target, ast.Name):
+                        variables.add(target.id)
+            elif (
+                isinstance(assignment, ast.AnnAssign)
+                and isinstance(assignment.target, ast.Name)
+            ):
+                variables.add(assignment.target.id)
+
+        return variables
+
+#######################################################################
+## Class used by 'Unused Arguments'
+##
+## adapted from: flake8-unused-arguments v0.0.13
+#######################################################################
+
 
 class FunctionFinder(ast.NodeVisitor):
     """
@@ -408,3 +475,70 @@
                     self.visit(obj)
 
     visit_AsyncFunctionDef = visit_FunctionDef = visit_Lambda = __visitFunctionTypes
+
+#######################################################################
+## Class used by 'Unused Globals'
+##
+## adapted from: flake8-unused-globals v0.1.9
+#######################################################################
+
+
+GlobalVariableStoreInfo = collections.namedtuple(
+    "GlobalVariableStoreInfo", ["lineno", "offset"]
+)
+
+
+class GlobalVariableLoadCounter(ast.NodeVisitor):
+    """
+    Class to find all defined global variables and count their usages.
+    """
+
+    def __init__(self):
+        """
+        Constructor
+        """
+        super().__init__()
+
+        self.__loads = {}
+        self.__storeInfo = {}
+
+    def visit_Name(self, nameNode):
+        """
+        Public method to record the definition and use of a global variable.
+
+        @param nameNode reference to the name node to be processed
+        @type ast.Name
+        """
+        if isinstance(nameNode.ctx, ast.Load) and nameNode.id in self.__loads:
+            self.__loads[nameNode.id] += 1
+        elif (
+            isinstance(nameNode.ctx, ast.Store)
+            and nameNode.id not in self.__storeInfo
+        ):
+            self.__loads[nameNode.id] = 0
+            self.__storeInfo[nameNode.id] = GlobalVariableStoreInfo(
+                lineno=nameNode.lineno, offset=nameNode.col_offset
+            )
+
+    def getLoads(self):
+        """
+        Public method to get an iterator of the detected variable loads.
+
+        @return DESCRIPTION
+        @rtype TYPE
+        """
+        return self.__loads.items()
+
+    def getStoreInfo(self, variableId):
+        """
+        Public method to get the store info data of a given variable ID.
+
+        @param variableId variable ID to retrieve the store info for
+        @type str
+        @return named tuple containing the line number and column offset
+        @rtype GlobalVariableStoreInfo
+        """
+        try:
+            return self.__storeInfo[variableId]
+        except KeyError:
+            return None

eric ide

mercurial