eric6/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py

branch
maintenance
changeset 8273
698ae46f40a4
parent 8259
2bbec88047dd
--- a/eric6/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py	Fri Apr 02 11:59:41 2021 +0200
+++ b/eric6/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py	Sat May 01 14:27:20 2021 +0200
@@ -10,6 +10,8 @@
 import os
 import fnmatch
 import copy
+import collections
+import json
 
 from PyQt5.QtCore import pyqtSlot, Qt, QTimer, QCoreApplication
 from PyQt5.QtGui import QIcon
@@ -31,11 +33,9 @@
 from .Miscellaneous.MiscellaneousDefaults import (
     MiscellaneousCheckerDefaultArgs
 )
-
-try:
-    basestring          # __IGNORE_WARNING__
-except Exception:
-    basestring = str    # define for Python3
+from .Annotations.AnnotationsCheckerDefaults import (
+    AnnotationsCheckerDefaultArgs
+)
 
 
 class CodeStyleCheckerDialog(QDialog, Ui_CodeStyleCheckerDialog):
@@ -91,6 +91,9 @@
         "W": QCoreApplication.translate(
             "CheckerCategories",
             "Warnings"),
+        "Y": QCoreApplication.translate(
+            "CheckerCategories",
+            "Simplify Code"),
     }
     
     noResults = 0
@@ -109,7 +112,7 @@
         @param parent reference to the parent widget
         @type QWidget
         """
-        super(CodeStyleCheckerDialog, self).__init__(parent)
+        super().__init__(parent)
         self.setupUi(self)
         self.setWindowFlags(Qt.WindowType.Window)
         
@@ -192,15 +195,28 @@
         self.__project = None
         self.__forProject = False
         self.__data = {}
-        self.__statistics = {}
+        self.__statistics = collections.defaultdict(self.__defaultStatistics)
         self.__onlyFixes = {}
         self.__noFixCodesList = []
+        self.__detectedCodes = []
         
         self.on_loadDefaultButton_clicked()
         
         self.mainWidget.setCurrentWidget(self.configureTab)
         self.optionsTabWidget.setCurrentWidget(self.globalOptionsTab)
     
+    def __defaultStatistics(self):
+        """
+        Private method to return the default statistics entry.
+        
+        @return dictionary with default statistics entry
+        @rtype dict
+        """
+        return {
+            "total": 0,
+            "ignored": 0,
+        }
+    
     def __resort(self):
         """
         Private method to resort the tree.
@@ -275,6 +291,7 @@
             self.__lastFileItem.setData(0, self.filenameRole, filename)
         
         msgCode = result["code"].split(".", 1)[0]
+        self.__detectedCodes.append(msgCode)
         
         fixable = False
         itm = QTreeWidgetItem(
@@ -292,6 +309,8 @@
             itm.setIcon(1, UI.PixmapCache.getIcon("docstringError"))
         elif msgCode.startswith("P"):
             itm.setIcon(1, UI.PixmapCache.getIcon("dirClosed"))
+        elif msgCode.startswith("Y"):
+            itm.setIcon(1, UI.PixmapCache.getIcon("filePython"))
         elif msgCode.startswith("S"):
             if "severity" in result:
                 if result["severity"] == "H":
@@ -374,16 +393,14 @@
         @type int
         """
         self.__statistics["_FilesCount"] += 1
-        stats = [k for k in statistics.keys() if k[0].isupper()]
+        stats = [k for k in statistics if k[0].isupper()]
         if stats:
             self.__statistics["_FilesIssues"] += 1
-            for key in statistics:
-                if key in self.__statistics:
-                    self.__statistics[key] += statistics[key]
-                else:
-                    self.__statistics[key] = statistics[key]
+            for key in stats:
+                self.__statistics[key]["total"] += statistics[key]
+            for key in ignoredErrors:
+                self.__statistics[key]["ignored"] += ignoredErrors[key]
         self.__statistics["_IssuesFixed"] += fixer
-        self.__statistics["_IgnoredErrors"] += ignoredErrors
         self.__statistics["_SecurityOK"] += securityOk
     
     def __updateFixerStatistics(self, fixer):
@@ -399,11 +416,10 @@
         """
         Private slot to reset the statistics data.
         """
-        self.__statistics = {}
+        self.__statistics.clear()
         self.__statistics["_FilesCount"] = 0
         self.__statistics["_FilesIssues"] = 0
         self.__statistics["_IssuesFixed"] = 0
-        self.__statistics["_IgnoredErrors"] = 0
         self.__statistics["_SecurityOK"] = 0
     
     def prepare(self, fileList, project):
@@ -497,10 +513,30 @@
             )
         
         if "AnnotationsChecker" not in self.__data:
-            self.__data["AnnotationsChecker"] = {
-                "MinimumCoverage": 75,
-                "MaximumComplexity": 3,
-            }
+            self.__data["AnnotationsChecker"] = copy.deepcopy(
+                AnnotationsCheckerDefaultArgs)
+        else:
+            # We are upgrading from an older data structure
+            if "MaximumLength" not in self.__data["AnnotationsChecker"]:
+                # MaximumLength is the sentinel for the first extension
+                self.__data["AnnotationsChecker"].update({
+                    "MaximumLength":
+                        AnnotationsCheckerDefaultArgs["MaximumLength"],
+                    "SuppressNoneReturning":
+                        AnnotationsCheckerDefaultArgs["SuppressNoneReturning"],
+                    "SuppressDummyArgs":
+                        AnnotationsCheckerDefaultArgs["SuppressDummyArgs"],
+                    "AllowUntypedDefs":
+                        AnnotationsCheckerDefaultArgs["AllowUntypedDefs"],
+                    "AllowUntypedNested":
+                        AnnotationsCheckerDefaultArgs["AllowUntypedNested"],
+                    "MypyInitReturn":
+                        AnnotationsCheckerDefaultArgs["MypyInitReturn"],
+                    "DispatchDecorators":
+                        AnnotationsCheckerDefaultArgs["DispatchDecorators"],
+                    "OverloadDecorators":
+                        AnnotationsCheckerDefaultArgs["OverloadDecorators"],
+                })
         
         if "SecurityChecker" not in self.__data:
             from .Security.SecurityDefaults import SecurityDefaults
@@ -557,10 +593,30 @@
             self.__data["CommentedCodeChecker"]["Aggressive"])
         self.__initCommentedCodeCheckerWhiteList(
             self.__data["CommentedCodeChecker"]["WhiteList"])
+        
+        # type annotations
         self.minAnnotationsCoverageSpinBox.setValue(
             self.__data["AnnotationsChecker"]["MinimumCoverage"])
         self.maxAnnotationsComplexitySpinBox.setValue(
             self.__data["AnnotationsChecker"]["MaximumComplexity"])
+        self.maxAnnotationsLengthSpinBox.setValue(
+            self.__data["AnnotationsChecker"]["MaximumLength"])
+        self.suppressNoneReturningCheckBox.setChecked(
+            self.__data["AnnotationsChecker"]["SuppressNoneReturning"])
+        self.suppressDummyArgsCheckBox.setChecked(
+            self.__data["AnnotationsChecker"]["SuppressDummyArgs"])
+        self.allowUntypedDefsCheckBox.setChecked(
+            self.__data["AnnotationsChecker"]["AllowUntypedDefs"])
+        self.allowUntypedNestedCheckBox.setChecked(
+            self.__data["AnnotationsChecker"]["AllowUntypedNested"])
+        self.mypyInitReturnCheckBox.setChecked(
+            self.__data["AnnotationsChecker"]["MypyInitReturn"])
+        self.dispatchDecoratorEdit.setText(
+            ", ".join(
+                self.__data["AnnotationsChecker"]["DispatchDecorators"]))
+        self.overloadDecoratorEdit.setText(
+            ", ".join(
+                self.__data["AnnotationsChecker"]["OverloadDecorators"]))
         
         # security
         self.tmpDirectoriesEdit.setPlainText("\n".join(
@@ -712,11 +768,30 @@
                     "WhiteList": self.__getCommentedCodeCheckerWhiteList(),
                 }
             }
+            
             annotationArgs = {
                 "MinimumCoverage":
                     self.minAnnotationsCoverageSpinBox.value(),
                 "MaximumComplexity":
                     self.maxAnnotationsComplexitySpinBox.value(),
+                "MaximumLength":
+                    self.maxAnnotationsLengthSpinBox.value(),
+                "SuppressNoneReturning":
+                    self.suppressNoneReturningCheckBox.isChecked(),
+                "SuppressDummyArgs":
+                    self.suppressDummyArgsCheckBox.isChecked(),
+                "AllowUntypedDefs":
+                    self.allowUntypedDefsCheckBox.isChecked(),
+                "AllowUntypedNested":
+                    self.allowUntypedNestedCheckBox.isChecked(),
+                "MypyInitReturn":
+                    self.mypyInitReturnCheckBox.isChecked(),
+                "DispatchDecorators":
+                    [d.strip()
+                     for d in self.dispatchDecoratorEdit.text().split(",")],
+                "OverloadDecorators":
+                    [d.strip()
+                     for d in self.overloadDecoratorEdit.text().split(",")],
             }
             
             securityArgs = {
@@ -785,7 +860,7 @@
         """
         options = self.__options[:]
         flags = Utilities.extractFlags(source)
-        if "noqa" in flags and isinstance(flags["noqa"], basestring):
+        if "noqa" in flags and isinstance(flags["noqa"], str):
             excludeMessages = options[0].strip().rstrip(",")
             if excludeMessages:
                 excludeMessages += ","
@@ -864,11 +939,9 @@
         self.__lastFileItem = None
         
         self.checkProgressLabel.setPath(self.tr("Preparing files..."))
-        progress = 0
         
         argumentsList = []
-        for filename in self.files:
-            progress += 1
+        for progress, filename in enumerate(self.files, start=1):
             self.checkProgress.setValue(progress)
             QApplication.processEvents()
             
@@ -957,7 +1030,7 @@
         self.resultList.setSortingEnabled(False)
         
         fixed = None
-        ignoredErrors = 0
+        ignoredErrors = collections.defaultdict(int)
         securityOk = 0
         if self.__itms:
             for itm, result in zip(self.__itms, results):
@@ -968,7 +1041,7 @@
             
             for result in results:
                 if result["ignored"]:
-                    ignoredErrors += 1
+                    ignoredErrors[result["code"]] += 1
                     if self.showIgnored:
                         result["display"] = self.tr(
                             "{0} (ignored)"
@@ -978,11 +1051,12 @@
                 
                 elif result["securityOk"]:
                     securityOk += 1
-                    continue
+                    if result["code"].startswith("S"):
+                        continue
                 
                 self.results = CodeStyleCheckerDialog.hasResults
                 self.__createResultItem(fn, result)
-
+            
             self.__updateStatistics(
                 codeStyleCheckerStats, fixes, ignoredErrors, securityOk)
         
@@ -1060,6 +1134,12 @@
                 QHeaderView.ResizeMode.ResizeToContents)
             self.resultList.header().setStretchLastSection(True)
             
+            if self.__detectedCodes:
+                self.filterComboBox.addItem("")
+                self.filterComboBox.addItems(sorted(set(self.__detectedCodes)))
+                self.filterComboBox.setEnabled(True)
+                self.filterButton.setEnabled(True)
+            
             self.checkProgress.setVisible(False)
             self.checkProgressLabel.setVisible(False)
             
@@ -1074,10 +1154,11 @@
         @return eol string
         @rtype str
         """
-        if self.__project.isOpen() and self.__project.isProjectFile(fn):
-            eol = self.__project.getEolString()
-        else:
-            eol = Utilities.linesep()
+        eol = (
+            self.__project.getEolString()
+            if self.__project.isOpen() and self.__project.isProjectFile(fn)
+            else Utilities.linesep()
+        )
         return eol
     
     @pyqtSlot()
@@ -1124,6 +1205,26 @@
                         self.minAnnotationsCoverageSpinBox.value(),
                     "MaximumComplexity":
                         self.maxAnnotationsComplexitySpinBox.value(),
+                    "MaximumLength":
+                        self.maxAnnotationsLengthSpinBox.value(),
+                    "SuppressNoneReturning":
+                        self.suppressNoneReturningCheckBox.isChecked(),
+                    "SuppressDummyArgs":
+                        self.suppressDummyArgsCheckBox.isChecked(),
+                    "AllowUntypedDefs":
+                        self.allowUntypedDefsCheckBox.isChecked(),
+                    "AllowUntypedNested":
+                        self.allowUntypedNestedCheckBox.isChecked(),
+                    "MypyInitReturn":
+                        self.mypyInitReturnCheckBox.isChecked(),
+                    "DispatchDecorators":
+                        [d.strip()
+                         for d in self.dispatchDecoratorEdit.text().split(",")
+                         ],
+                    "OverloadDecorators":
+                        [d.strip()
+                         for d in self.overloadDecoratorEdit.text().split(",")
+                         ],
                 },
                 "SecurityChecker": {
                     "HardcodedTmpDirectories": [
@@ -1156,7 +1257,10 @@
                         self.typedExceptionsCheckBox.isChecked(),
                 },
             }
-            if data != self.__data:
+            if (
+                json.dumps(data, sort_keys=True) !=
+                json.dumps(self.__data, sort_keys=True)
+            ):
                 self.__data = data
                 self.__project.setData("CHECKERSPARMS", "Pep8Checker",
                                        self.__data)
@@ -1164,6 +1268,10 @@
         self.resultList.clear()
         self.results = CodeStyleCheckerDialog.noResults
         self.cancelled = False
+        self.__detectedCodes.clear()
+        self.filterComboBox.clear()
+        self.filterComboBox.setEnabled(False)
+        self.filterButton.setEnabled(False)
         
         self.start(self.__fileOrFileList)
     
@@ -1404,12 +1512,48 @@
                     "CommentedCodeChecker"]["WhiteList"]
             )
         ))
+        
+        # type annotations
         self.minAnnotationsCoverageSpinBox.setValue(int(
             Preferences.Prefs.settings.value(
-                "PEP8/MinimumAnnotationsCoverage", 75)))
+                "PEP8/MinimumAnnotationsCoverage",
+                AnnotationsCheckerDefaultArgs["MinimumCoverage"])))
         self.maxAnnotationsComplexitySpinBox.setValue(int(
             Preferences.Prefs.settings.value(
-                "PEP8/MaximumAnnotationComplexity", 3)))
+                "PEP8/MaximumAnnotationComplexity",
+                AnnotationsCheckerDefaultArgs["MaximumComplexity"])))
+        self.maxAnnotationsLengthSpinBox.setValue(int(
+            Preferences.Prefs.settings.value(
+                "PEP8/MaximumAnnotationLength",
+                AnnotationsCheckerDefaultArgs["MaximumLength"])))
+        self.suppressNoneReturningCheckBox.setChecked(Preferences.toBool(
+            Preferences.Prefs.settings.value(
+                "PEP8/SuppressNoneReturning",
+                AnnotationsCheckerDefaultArgs["SuppressNoneReturning"])))
+        self.suppressDummyArgsCheckBox.setChecked(Preferences.toBool(
+            Preferences.Prefs.settings.value(
+                "PEP8/SuppressDummyArgs",
+                AnnotationsCheckerDefaultArgs["SuppressDummyArgs"])))
+        self.allowUntypedDefsCheckBox.setChecked(Preferences.toBool(
+            Preferences.Prefs.settings.value(
+                "PEP8/AllowUntypedDefs",
+                AnnotationsCheckerDefaultArgs["AllowUntypedDefs"])))
+        self.allowUntypedNestedCheckBox.setChecked(Preferences.toBool(
+            Preferences.Prefs.settings.value(
+                "PEP8/AllowUntypedNested",
+                AnnotationsCheckerDefaultArgs["AllowUntypedNested"])))
+        self.mypyInitReturnCheckBox.setChecked(Preferences.toBool(
+            Preferences.Prefs.settings.value(
+                "PEP8/MypyInitReturn",
+                AnnotationsCheckerDefaultArgs["MypyInitReturn"])))
+        self.dispatchDecoratorEdit.setText(", ".join(Preferences.toList(
+            Preferences.Prefs.settings.value(
+                "PEP8/DispatchDecorators",
+                AnnotationsCheckerDefaultArgs["DispatchDecorators"]))))
+        self.overloadDecoratorEdit.setText(", ".join(Preferences.toList(
+            Preferences.Prefs.settings.value(
+                "PEP8/OverloadDecorators",
+                AnnotationsCheckerDefaultArgs["OverloadDecorators"]))))
         
         # security
         from .Security.SecurityDefaults import SecurityDefaults
@@ -1517,12 +1661,40 @@
         Preferences.Prefs.settings.setValue(
             "PEP8/CommentedCodeWhitelist",
             self.__getCommentedCodeCheckerWhiteList())
+        
+        # type annotations
         Preferences.Prefs.settings.setValue(
             "PEP8/MinimumAnnotationsCoverage",
             self.minAnnotationsCoverageSpinBox.value())
         Preferences.Prefs.settings.setValue(
             "PEP8/MaximumAnnotationComplexity",
             self.maxAnnotationsComplexitySpinBox.value())
+        Preferences.Prefs.settings.setValue(
+            "PEP8/MaximumAnnotationLength",
+            self.maxAnnotationsLengthSpinBox.value())
+        Preferences.Prefs.settings.setValue(
+            "PEP8/SuppressNoneReturning",
+            self.suppressNoneReturningCheckBox.isChecked())
+        Preferences.Prefs.settings.setValue(
+            "PEP8/SuppressDummyArgs",
+            self.suppressDummyArgsCheckBox.isChecked())
+        Preferences.Prefs.settings.setValue(
+            "PEP8/AllowUntypedDefs",
+            self.allowUntypedDefsCheckBox.isChecked())
+        Preferences.Prefs.settings.setValue(
+            "PEP8/AllowUntypedNested",
+            self.allowUntypedNestedCheckBox.isChecked())
+        Preferences.Prefs.settings.setValue(
+            "PEP8/MypyInitReturn",
+            self.mypyInitReturnCheckBox.isChecked())
+        Preferences.Prefs.settings.setValue(
+            "PEP8/DispatchDecorators",
+            [d.strip()
+             for d in self.dispatchDecoratorEdit.text().split(",")])
+        Preferences.Prefs.settings.setValue(
+            "PEP8/OverloadDecorators",
+            [d.strip()
+             for d in self.overloadDecoratorEdit.text().split(",")])
         
         # security
         Preferences.Prefs.settings.setValue(
@@ -1620,10 +1792,38 @@
             MiscellaneousCheckerDefaultArgs[
                 "CommentedCodeChecker"]["WhiteList"]
         )
+        
+        # type annotations
         Preferences.Prefs.settings.setValue(
-            "PEP8/MinimumAnnotationsCoverage", 75)
+            "PEP8/MinimumAnnotationsCoverage",
+            AnnotationsCheckerDefaultArgs["MinimumCoverage"])
+        Preferences.Prefs.settings.setValue(
+            "PEP8/MaximumAnnotationComplexity",
+            AnnotationsCheckerDefaultArgs["MaximumComplexity"])
+        Preferences.Prefs.settings.setValue(
+            "PEP8/MaximumAnnotationLength",
+            AnnotationsCheckerDefaultArgs["MaximumLength"])
+        Preferences.Prefs.settings.setValue(
+            "PEP8/SuppressNoneReturning",
+            AnnotationsCheckerDefaultArgs["SuppressNoneReturning"])
+        Preferences.Prefs.settings.setValue(
+            "PEP8/SuppressDummyArgs",
+            AnnotationsCheckerDefaultArgs["SuppressDummyArgs"])
         Preferences.Prefs.settings.setValue(
-            "PEP8/MaximumAnnotationComplexity", 3)
+            "PEP8/AllowUntypedDefs",
+            AnnotationsCheckerDefaultArgs["AllowUntypedDefs"])
+        Preferences.Prefs.settings.setValue(
+            "PEP8/AllowUntypedNested",
+            AnnotationsCheckerDefaultArgs["AllowUntypedNested"])
+        Preferences.Prefs.settings.setValue(
+            "PEP8/MypyInitReturn",
+            AnnotationsCheckerDefaultArgs["MypyInitReturn"])
+        Preferences.Prefs.settings.setValue(
+            "PEP8/DispatchDecorators",
+            AnnotationsCheckerDefaultArgs["DispatchDecorators"])
+        Preferences.Prefs.settings.setValue(
+            "PEP8/OverloadDecorators",
+            AnnotationsCheckerDefaultArgs["OverloadDecorators"])
         
         # security
         from .Security.SecurityDefaults import SecurityDefaults
@@ -1779,12 +1979,11 @@
         @param selectedFutures comma separated list of expected future imports
         @type str
         """
-        if selectedFutures:
-            expectedImports = [
-                i.strip() for i in selectedFutures.split(",")
-                if bool(i.strip())]
-        else:
-            expectedImports = []
+        expectedImports = (
+            [i.strip() for i in selectedFutures.split(",") if bool(i.strip())]
+            if selectedFutures else
+            []
+        )
         for row in range(self.futuresList.count()):
             itm = self.futuresList.item(row)
             if itm.text() in expectedImports:
@@ -1878,13 +2077,12 @@
             categories
         @type str
         """
-        if enabledCategories:
-            enabledCategoriesList = [
-                c.strip() for c in enabledCategories.split(",")
-                if bool(c.strip())]
-        else:
-            enabledCategoriesList = list(
-                CodeStyleCheckerDialog.checkCategories.keys())
+        enabledCategoriesList = (
+            [c.strip() for c in enabledCategories.split(",")
+             if bool(c.strip())]
+            if enabledCategories else
+            list(CodeStyleCheckerDialog.checkCategories.keys())
+        )
         for row in range(self.categoriesList.count()):
             itm = self.categoriesList.item(row)
             if itm.data(Qt.ItemDataRole.UserRole) in enabledCategoriesList:
@@ -2023,3 +2221,27 @@
             row = self.whitelistWidget.row(itm)
             self.whitelistWidget.takeItem(row)
             del itm
+    
+    @pyqtSlot()
+    def on_filterButton_clicked(self):
+        """
+        Private slot to filter the list of messages based on selected message
+        code.
+        """
+        selectedMessageCode = self.filterComboBox.currentText()
+        
+        for topRow in range(self.resultList.topLevelItemCount()):
+            topItem = self.resultList.topLevelItem(topRow)
+            topItem.setExpanded(True)
+            visibleChildren = topItem.childCount()
+            for childIndex in range(topItem.childCount()):
+                childItem = topItem.child(childIndex)
+                hideChild = (
+                    childItem.data(0, self.codeRole) != selectedMessageCode
+                    if selectedMessageCode else
+                    False
+                )
+                childItem.setHidden(hideChild)
+                if hideChild:
+                    visibleChildren -= 1
+            topItem.setHidden(visibleChildren == 0)

eric ide

mercurial