Code Style Checker eric7

Tue, 23 May 2023 12:00:37 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 23 May 2023 12:00:37 +0200
branch
eric7
changeset 10053
9914b7b4b11c
parent 10052
041d0785dd42
child 10054
d7a47f0cff2b

Code Style Checker
- Added a checker for unused global variables.

eric7.epj file | annotate | diff | comparison | revisions
src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py file | annotate | diff | comparison | revisions
src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.ui file | annotate | diff | comparison | revisions
src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Unused/UnusedChecker.py file | annotate | diff | comparison | revisions
src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Unused/translations.py file | annotate | diff | comparison | revisions
--- 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"],
 }

eric ide

mercurial