--- 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