Tue, 23 May 2023 12:00:37 +0200
Code Style Checker
- Added a checker for unused global variables.
--- a/eric7.epj Mon May 22 19:53:41 2023 +0200 +++ b/eric7.epj Tue May 23 12:00:37 2023 +0200 @@ -128,6 +128,7 @@ "ShowIgnored": false, "UnusedChecker": { "IgnoreAbstract": true, + "IgnoreDunderGlobals": true, "IgnoreDunderMethods": true, "IgnoreLambdas": false, "IgnoreNestedFunctions": false,
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py Mon May 22 19:53:41 2023 +0200 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py Tue May 23 12:00:37 2023 +0200 @@ -505,17 +505,17 @@ "SortFromFirst": False, }, # Unused - # TODO: add 'IgnoreSlotMethods' (pyqtSlot, Slot) "UnusedChecker": { - "IgnoreAbstract": False, - "IgnoreOverload": False, - "IgnoreOverride": False, + "IgnoreAbstract": True, + "IgnoreOverload": True, + "IgnoreOverride": True, "IgnoreSlotMethods": False, - "IgnoreStubs": False, + "IgnoreStubs": True, "IgnoreVariadicNames": False, "IgnoreLambdas": False, "IgnoreNestedFunctions": False, - "IgnoreDunderMethods": False, + "IgnoreDunderMethods": True, + "IgnoreDunderGlobals": True, }, } @@ -763,6 +763,9 @@ self.ignoreSlotsCheckBox.setChecked( self.__data["UnusedChecker"]["IgnoreSlotMethods"] ) + self.ignoreDunderGlobalsCheckBox.setChecked( + self.__data["UnusedChecker"]["IgnoreDunderGlobals"] + ) self.__cleanupData() @@ -966,6 +969,7 @@ "IgnoreNestedFunctions": self.ignoreNestedFunctionsCheckBox.isChecked(), "IgnoreDunderMethods": self.ignoreDunderMethodsCheckBox.isChecked(), "IgnoreSlotMethods": self.ignoreSlotsCheckBox.isChecked(), + "IgnoreDunderGlobals": self.ignoreDunderGlobalsCheckBox.isChecked(), } self.__options = [ @@ -1431,6 +1435,7 @@ ), "IgnoreDunderMethods": self.ignoreDunderMethodsCheckBox.isChecked(), "IgnoreSlotMethods": self.ignoreSlotsCheckBox.isChecked(), + "IgnoreDunderGlobals": self.ignoreDunderGlobalsCheckBox.isChecked(), }, } if json.dumps(data, sort_keys=True) != json.dumps( @@ -2084,6 +2089,14 @@ ) ) ) + self.ignoreDunderGlobalsCheckBox.setChecked( + Preferences.toBool( + settings.value( + "PRP8/UnusedIgnoreDunderGlobals", + defaultParameters["UnusedChecker"]["IgnoreDunderGlobals"], + ) + ) + ) self.__cleanupData() @@ -2272,6 +2285,10 @@ settings.setValue( "PEP8/UnusedIgnoreSlotMethods", self.ignoreSlotsCheckBox.isChecked() ) + settings.setValue( + "PEP8/UnusedIgnoreDunderGlobals", + self.ignoreDunderGlobalsCheckBox.isChecked(), + ) @pyqtSlot() def on_resetDefaultButton_clicked(self): @@ -2497,6 +2514,10 @@ "PEP8/UnusedIgnoreSlotMethods", defaultParameters["UnusedChecker"]["IgnoreSlotMethods"], ) + settings.setValue( + "PEP8/UnusedIgnoreDunderGlobals", + defaultParameters["UnusedChecker"]["IgnoreDunderGlobals"], + ) # Update UI with default values self.on_loadDefaultButton_clicked()
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.ui Mon May 22 19:53:41 2023 +0200 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.ui Tue May 23 12:00:37 2023 +0200 @@ -1554,6 +1554,16 @@ <string>Ignore Warnings For</string> </property> <layout class="QGridLayout" name="gridLayout_11"> + <item row="2" column="1"> + <widget class="QCheckBox" name="ignoreLambdasCheckBox"> + <property name="toolTip"> + <string>Ignore unused arguments for lambda functions.</string> + </property> + <property name="text"> + <string>Lambda Functions</string> + </property> + </widget> + </item> <item row="0" column="0"> <widget class="QCheckBox" name="ignoreAbstractCheckBox"> <property name="toolTip"> @@ -1564,6 +1574,36 @@ </property> </widget> </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="ignoreVariadicNamesCheckBox"> + <property name="toolTip"> + <string>Ignore unused *args and **kwargs.</string> + </property> + <property name="text"> + <string>*args and **kwargs</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="ignoreStubsCheckBox"> + <property name="toolTip"> + <string>Ignore unused arguments for methods consisting of a pass statement only.</string> + </property> + <property name="text"> + <string>Stub Methods</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="ignoreOverrideCheckBox"> + <property name="toolTip"> + <string>Ignore unused arguments for methods decorated with '@override'.</string> + </property> + <property name="text"> + <string>Override Methods</string> + </property> + </widget> + </item> <item row="0" column="1"> <widget class="QCheckBox" name="ignoreOverloadCheckBox"> <property name="toolTip"> @@ -1574,43 +1614,13 @@ </property> </widget> </item> - <item row="1" column="0"> - <widget class="QCheckBox" name="ignoreOverrideCheckBox"> + <item row="4" column="1"> + <widget class="QCheckBox" name="ignoreDunderGlobalsCheckBox"> <property name="toolTip"> - <string>Ignore unused arguments for methods decorated with '@override'.</string> - </property> - <property name="text"> - <string>Override Methods</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QCheckBox" name="ignoreStubsCheckBox"> - <property name="toolTip"> - <string>Ignore unused arguments for methods consisting of a pass statement only.</string> + <string>Ignore unused global variables starting and ending with double underscores.</string> </property> <property name="text"> - <string>Stub Methods</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QCheckBox" name="ignoreVariadicNamesCheckBox"> - <property name="toolTip"> - <string>Ignore unused *args and **kwargs.</string> - </property> - <property name="text"> - <string>*args and **kwargs</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QCheckBox" name="ignoreLambdasCheckBox"> - <property name="toolTip"> - <string>Ignore unused arguments for lambda functions.</string> - </property> - <property name="text"> - <string>Lambda Functions</string> + <string>Special Global Variables</string> </property> </widget> </item> @@ -1625,6 +1635,16 @@ </widget> </item> <item row="3" column="1"> + <widget class="QCheckBox" name="ignoreSlotsCheckBox"> + <property name="toolTip"> + <string>Ignore unused arguments for methods decorated with '@pyqtSlot' or '@Slot'.</string> + </property> + <property name="text"> + <string>Qt Slot Methods</string> + </property> + </widget> + </item> + <item row="4" column="0"> <widget class="QCheckBox" name="ignoreDunderMethodsCheckBox"> <property name="toolTip"> <string>Ignore unused arguments for methods starting and ending with double underscores.</string> @@ -1634,16 +1654,6 @@ </property> </widget> </item> - <item row="4" column="0"> - <widget class="QCheckBox" name="ignoreSlotsCheckBox"> - <property name="toolTip"> - <string>Ignore unused arguments for methods decorated with '@pyqtSlot' or '@Slot'.</string> - </property> - <property name="text"> - <string>Qt Slot Methods</string> - </property> - </widget> - </item> </layout> </widget> </item> @@ -2074,8 +2084,9 @@ <tabstop>ignoreVariadicNamesCheckBox</tabstop> <tabstop>ignoreLambdasCheckBox</tabstop> <tabstop>ignoreNestedFunctionsCheckBox</tabstop> + <tabstop>ignoreSlotsCheckBox</tabstop> <tabstop>ignoreDunderMethodsCheckBox</tabstop> - <tabstop>ignoreSlotsCheckBox</tabstop> + <tabstop>ignoreDunderGlobalsCheckBox</tabstop> <tabstop>startButton</tabstop> <tabstop>loadDefaultButton</tabstop> <tabstop>storeDefaultButton</tabstop>
--- 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
--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Unused/translations.py Mon May 22 19:53:41 2023 +0200 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Unused/translations.py Tue May 23 12:00:37 2023 +0200 @@ -10,11 +10,17 @@ from PyQt6.QtCore import QCoreApplication _unusedMessages = { + ## Unused Arguments "U100": QCoreApplication.translate("UnusedChecker", "Unused argument '{0}'"), "U101": QCoreApplication.translate("UnusedChecker", "Unused argument '{0}'"), + ## Unused Globals + "U200": QCoreApplication.translate("UnusedChecker", "Unused global variable '{0}'"), } _unusedMessagesSampleArgs = { + ## Unused Arguments "U100": ["foo_arg"], "U101": ["_bar_arg"], + ## Unused Globals + "U200": ["FOO"], }