eric6/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py

changeset 7609
d5aff4fd0ef8
parent 7600
d2bf0476484b
child 7610
df7025fe26a3
--- a/eric6/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py	Sun May 31 17:23:49 2020 +0200
+++ b/eric6/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py	Thu Jun 04 17:57:20 2020 +0200
@@ -11,7 +11,7 @@
 import os
 import fnmatch
 
-from PyQt5.QtCore import pyqtSlot, Qt, QTimer
+from PyQt5.QtCore import pyqtSlot, Qt, QTimer, QCoreApplication
 from PyQt5.QtGui import QIcon
 from PyQt5.QtWidgets import (
     QDialog, QTreeWidgetItem, QAbstractButton, QDialogButtonBox, QApplication,
@@ -51,6 +51,30 @@
         'print_function', 'unicode_literals', 'generator_stop',
         'annotations']
     
+    checkCategories = {
+        "A": QCoreApplication.translate(
+            "CheckerCategories",
+            "Annotations"),
+        "C": QCoreApplication.translate(
+            "CheckerCategories",
+            "Code Complexity"),
+        "D": QCoreApplication.translate(
+            "CheckerCategories",
+            "Documentation"),
+        "E": QCoreApplication.translate(
+            "CheckerCategories",
+            "Errors"),
+        "M": QCoreApplication.translate(
+            "CheckerCategories",
+            "Miscellaneous"),
+        "N": QCoreApplication.translate(
+            "CheckerCategories",
+            "Naming"),
+        "W": QCoreApplication.translate(
+            "CheckerCategories",
+            "Warnings"),
+    }
+    
     noResults = 0
     noFiles = 1
     hasResults = 2
@@ -60,8 +84,9 @@
         Constructor
         
         @param styleCheckService reference to the service
-            (CodeStyleCheckService)
-        @param parent reference to the parent widget (QWidget)
+        @type CodeStyleCheckService
+        @param parent reference to the parent widget
+        @type QWidget
         """
         super(CodeStyleCheckerDialog, self).__init__(parent)
         self.setupUi(self)
@@ -81,6 +106,12 @@
         self.docTypeComboBox.addItem(self.tr("PEP-257"), "pep257")
         self.docTypeComboBox.addItem(self.tr("Eric"), "eric")
         
+        for category, text in CodeStyleCheckerDialog.checkCategories.items():
+            itm = QListWidgetItem(text, self.categoriesList)
+            itm.setData(Qt.UserRole, category)
+            itm.setFlags(itm.flags() | Qt.ItemIsUserCheckable)
+            itm.setCheckState(Qt.Unchecked)
+        
         for future in CodeStyleCheckerDialog.availableFutures:
             itm = QListWidgetItem(future, self.futuresList)
             itm.setFlags(itm.flags() | Qt.ItemIsUserCheckable)
@@ -173,15 +204,23 @@
         """
         Private method to create an entry in the result list.
         
-        @param filename file name of the file (string)
-        @param line line number of issue (integer or string)
-        @param pos character position of issue (integer or string)
-        @param message message text (string)
-        @param fixed flag indicating a fixed issue (boolean)
+        @param filename file name of the file
+        @type str
+        @param line line number of issue
+        @type int or str
+        @param pos character position of issue
+        @type int or str
+        @param message message text
+        @type str
+        @param fixed flag indicating a fixed issue
+        @type bool
         @param autofixing flag indicating, that we are fixing issues
-            automatically (boolean)
-        @param ignored flag indicating an ignored issue (boolean)
-        @return reference to the created item (QTreeWidgetItem)
+            automatically
+        @type bool
+        @param ignored flag indicating an ignored issue
+        @type bool
+        @return reference to the created item
+        @rtype QTreeWidgetItem
         """
         from .CodeStyleFixer import FixableCodeStyleIssues
         
@@ -200,7 +239,7 @@
             ["{0:6}".format(line), code, message])
         if code.startswith(("W", "-", "C", "M")):
             itm.setIcon(1, UI.PixmapCache.getIcon("warning"))
-        elif code.startswith("N"):
+        elif code.startswith(("A", "N")):
             itm.setIcon(1, UI.PixmapCache.getIcon("namingError"))
         elif code.startswith("D"):
             itm.setIcon(1, UI.PixmapCache.getIcon("docstringError"))
@@ -243,9 +282,12 @@
         Private method to modify a result list entry to show its
         positive fixed state.
         
-        @param itm reference to the item to modify (QTreeWidgetItem)
-        @param text text to be appended (string)
-        @param fixed flag indicating a fixed issue (boolean)
+        @param itm reference to the item to modify
+        @type QTreeWidgetItem
+        @param text text to be appended
+        @type str
+        @param fixed flag indicating a fixed issue
+        @type bool
         """
         if fixed:
             code, message = text.split(None, 1)
@@ -263,8 +305,11 @@
         
         @param statistics dictionary of statistical data with
             message code as key and message count as value
-        @param fixer reference to the code style fixer (CodeStyleFixer)
-        @param ignoredErrors number of ignored errors (integer)
+        @type dict
+        @param fixer reference to the code style fixer
+        @type CodeStyleFixer
+        @param ignoredErrors number of ignored errors
+        @type int
         """
         self.__statistics["_FilesCount"] += 1
         stats = [k for k in statistics.keys() if k[0].isupper()]
@@ -282,7 +327,8 @@
         """
         Private method to update the collected fixer related statistics.
         
-        @param fixer reference to the code style fixer (CodeStyleFixer)
+        @param fixer reference to the code style fixer
+        @type CodeStyleFixer
         """
         self.__statistics["_IssuesFixed"] += fixer
     
@@ -300,8 +346,10 @@
         """
         Public method to prepare the dialog with a list of filenames.
         
-        @param fileList list of filenames (list of strings)
-        @param project reference to the project object (Project)
+        @param fileList list of filenames
+        @type list of str
+        @param project reference to the project object
+        @type Project
         """
         self.__fileOrFileList = fileList[:]
         self.__project = project
@@ -325,6 +373,9 @@
                 "FixCodes": "",
                 "FixIssues": False,
             }
+        if "EnabledCheckerCategories" not in self.__data:
+            self.__data["EnabledCheckerCategories"] = ",".join(
+                CodeStyleCheckerDialog.checkCategories.keys())
         if "MaxLineLength" not in self.__data:
             self.__data["MaxLineLength"] = pycodestyle.MAX_LINE_LENGTH
         if "MaxDocLineLength" not in self.__data:
@@ -372,6 +423,7 @@
                 "MaximumComplexity": 3,
             }
         
+        self.__initCategoriesList(self.__data["EnabledCheckerCategories"])
         self.excludeFilesEdit.setText(self.__data["ExcludeFiles"])
         self.excludeMessagesEdit.setText(self.__data["ExcludeMessages"])
         self.includeMessagesEdit.setText(self.__data["IncludeMessages"])
@@ -403,17 +455,19 @@
             self.__data["AnnotationsChecker"]["MinimumCoverage"])
         self.maxAnnotationsComplexitySpinBox.setValue(
             self.__data["AnnotationsChecker"]["MaximumComplexity"])
+        
+        self.__cleanupData()
     
     def start(self, fn, save=False, repeat=None):
         """
         Public slot to start the code style check.
         
         @param fn file or list of files or directory to be checked
-                (string or list of strings)
-        @keyparam save flag indicating to save the given
-            file/file list/directory (boolean)
-        @keyparam repeat state of the repeat check box if it is not None
-            (None or boolean)
+        @type str or list of str
+        @param save flag indicating to save the given file/file list/directory
+        @type bool
+        @param repeat state of the repeat check box if it is not None
+        @type None or bool
         """
         if self.__project is None:
             self.__project = e5App().getObject("Project")
@@ -463,6 +517,7 @@
         self.__errorItem = None
         self.__resetStatistics()
         self.__clearErrors(self.files)
+        self.__cleanupData()
         
         if len(self.files) > 0:
             self.checkProgress.setMaximum(len(self.files))
@@ -471,7 +526,7 @@
             QApplication.processEvents()
             
             # extract the configuration values
-            excludeMessages = self.excludeMessagesEdit.text()
+            excludeMessages = self.__assembleExcludeMessages()
             includeMessages = self.includeMessagesEdit.text()
             repeatMessages = self.repeatCheckBox.isChecked()
             fixCodes = self.fixIssuesEdit.text()
@@ -544,8 +599,10 @@
         at the end of the source in order to extend the list of excluded
         messages for one file only.
         
-        @param source source text (list of str or str)
+        @param source source text
+        @type list of str or str
         @return list of checker options
+        @rtype list
         """
         options = self.__options[:]
         flags = Utilities.extractFlags(source)
@@ -563,7 +620,8 @@
         
         The results are reported to the __processResult slot.
         
-        @keyparam codestring optional sourcestring (str)
+        @param codestring optional sourcestring
+        @type str
         """
         if not self.files:
             self.checkProgressLabel.setPath("")
@@ -704,12 +762,15 @@
         """
         Private slot called after perfoming a style check on one file.
         
-        @param fn filename of the just checked file (str)
-        @param codeStyleCheckerStats stats of style and name check (dict)
-        @param fixes number of applied fixes (int)
+        @param fn filename of the just checked file
+        @type str
+        @param codeStyleCheckerStats stats of style and name check
+        @type dict
+        @param fixes number of applied fixes
+        @type int
         @param results tuple for each found violation of style (tuple of
-            lineno (int), position (int), text (str), ignored (bool),
-            fixed (bool), autofixing (bool))
+            lineno, position, text, ignored, fixed, autofixing)
+        @type tuplt of tuple of (int, int, str, bool, bool, bool)
         """
         if self.__finished:
             return
@@ -807,8 +868,10 @@
         """
         Private method to get the applicable eol string.
         
-        @param fn filename where to determine the line ending (str)
-        @return eol string (string)
+        @param fn filename where to determine the line ending
+        @type str
+        @return eol string
+        @rtype str
         """
         if self.__project.isOpen() and self.__project.isProjectFile(fn):
             eol = self.__project.getEolString()
@@ -821,8 +884,11 @@
         """
         Private slot to start a code style check run.
         """
+        self.__cleanupData()
+        
         if self.__forProject:
             data = {
+                "EnabledCheckerCategories": self.__getCategories(True),
                 "ExcludeFiles": self.excludeFilesEdit.text(),
                 "ExcludeMessages": self.excludeMessagesEdit.text(),
                 "IncludeMessages": self.includeMessagesEdit.text(),
@@ -876,16 +942,21 @@
         """
         self.on_startButton_clicked()
     
-    def __selectCodes(self, edit, showFixCodes):
+    def __selectCodes(self, edit, categories, showFixCodes):
         """
         Private method to select message codes via a selection dialog.
         
-        @param edit reference of the line edit to be populated (QLineEdit)
+        @param edit reference of the line edit to be populated
+        @type QLineEdit
+        @param categories list of message categories to omit
+        @type list of str
         @param showFixCodes flag indicating to show a list of fixable
-            issues (boolean)
+            issues
+        @type bool
         """
         from .CodeStyleCodeSelectionDialog import CodeStyleCodeSelectionDialog
-        dlg = CodeStyleCodeSelectionDialog(edit.text(), showFixCodes, self)
+        dlg = CodeStyleCodeSelectionDialog(edit.text(), categories,
+                                           showFixCodes, self)
         if dlg.exec_() == QDialog.Accepted:
             edit.setText(dlg.getSelectedCodes())
     
@@ -895,7 +966,9 @@
         Private slot to select the message codes to be excluded via a
         selection dialog.
         """
-        self.__selectCodes(self.excludeMessagesEdit, False)
+        self.__selectCodes(self.excludeMessagesEdit,
+                           self.__getCategories(False, asList=True),
+                           False)
     
     @pyqtSlot()
     def on_includeMessagesSelectButton_clicked(self):
@@ -903,7 +976,9 @@
         Private slot to select the message codes to be included via a
         selection dialog.
         """
-        self.__selectCodes(self.includeMessagesEdit, False)
+        self.__selectCodes(self.includeMessagesEdit,
+                           self.__getCategories(True, asList=True),
+                           False)
     
     @pyqtSlot()
     def on_fixIssuesSelectButton_clicked(self):
@@ -911,7 +986,7 @@
         Private slot to select the issue codes to be fixed via a
         selection dialog.
         """
-        self.__selectCodes(self.fixIssuesEdit, True)
+        self.__selectCodes(self.fixIssuesEdit, [], True)
     
     @pyqtSlot()
     def on_noFixIssuesSelectButton_clicked(self):
@@ -919,15 +994,17 @@
         Private slot to select the issue codes not to be fixed via a
         selection dialog.
         """
-        self.__selectCodes(self.noFixIssuesEdit, True)
+        self.__selectCodes(self.noFixIssuesEdit, [], True)
     
     @pyqtSlot(QTreeWidgetItem, int)
     def on_resultList_itemActivated(self, item, column):
         """
         Private slot to handle the activation of an item.
         
-        @param item reference to the activated item (QTreeWidgetItem)
-        @param column column the item was activated in (integer)
+        @param item reference to the activated item
+        @type QTreeWidgetItem
+        @param column column the item was activated in
+        @type int
         """
         if self.results != CodeStyleCheckerDialog.hasResults:
             return
@@ -1014,6 +1091,9 @@
         """
         Private slot to load the default configuration values.
         """
+        self.__initCategoriesList(Preferences.Prefs.settings.value(
+            "PEP8/EnabledCheckerCategories",
+            ",".join(CodeStyleCheckerDialog.checkCategories.keys())))
         self.excludeFilesEdit.setText(Preferences.Prefs.settings.value(
             "PEP8/ExcludeFilePatterns", ""))
         self.excludeMessagesEdit.setText(Preferences.Prefs.settings.value(
@@ -1075,6 +1155,8 @@
         self.maxAnnotationsComplexitySpinBox.setValue(int(
             Preferences.Prefs.settings.value(
                 "PEP8/MaximumAnnotationComplexity", 3)))
+        
+        self.__cleanupData()
     
     @pyqtSlot()
     def on_storeDefaultButton_clicked(self):
@@ -1083,6 +1165,8 @@
         default values.
         """
         Preferences.Prefs.settings.setValue(
+            "PEP8/EnabledCheckerCategories", self.__getCategories(True))
+        Preferences.Prefs.settings.setValue(
             "PEP8/ExcludeFilePatterns", self.excludeFilesEdit.text())
         Preferences.Prefs.settings.setValue(
             "PEP8/ExcludeMessages", self.excludeMessagesEdit.text())
@@ -1144,6 +1228,9 @@
         """
         Private slot to reset the configuration values to their default values.
         """
+        Preferences.Prefs.settings.setValue(
+            "PEP8/EnabledCheckerCategories",
+            ",".join(CodeStyleCheckerDialog.checkCategories.keys()))
         Preferences.Prefs.settings.setValue("PEP8/ExcludeFilePatterns", "")
         Preferences.Prefs.settings.setValue(
             "PEP8/ExcludeMessages", pycodestyle.DEFAULT_IGNORE)
@@ -1190,7 +1277,8 @@
         """
         Private slot called by a button of the button box clicked.
         
-        @param button button that was clicked (QAbstractButton)
+        @param button button that was clicked
+        @type QAbstractButton
         """
         if button == self.buttonBox.button(QDialogButtonBox.Close):
             self.close()
@@ -1210,7 +1298,8 @@
         Private method to clear all warning markers of open editors to be
         checked.
         
-        @param files list of files to be checked (list of string)
+        @param files list of files to be checked
+        @type list of str
         """
         vm = e5App().getObject("ViewManager")
         openFiles = vm.getOpenFilenames()
@@ -1224,7 +1313,7 @@
         Private slot to fix selected issues.
         
         Build a dictionary of issues to fix. Update the initialized __options.
-            Then call check with the dict as keyparam to fix selected issues.
+        Then call check with the dict as keyparam to fix selected issues.
         """
         fixableItems = self.__getSelectedFixableItems()
         # dictionary of lists of tuples containing the issue and the item
@@ -1260,7 +1349,8 @@
         """
         Private method to extract all selected items for fixable issues.
         
-        @return selected items for fixable issues (list of QTreeWidgetItem)
+        @return selected items for fixable issues
+        @rtype list of QTreeWidgetItem
         """
         fixableItems = []
         for itm in self.resultList.selectedItems():
@@ -1278,8 +1368,10 @@
         """
         Private method to check, if an item has a fixable issue.
         
-        @param itm item to be checked (QTreeWidgetItem)
-        @return flag indicating a fixable issue (boolean)
+        @param itm item to be checked
+        @type QTreeWidgetItem
+        @return flag indicating a fixable issue
+        @rtype bool
         """
         return (itm.data(0, self.fixableRole) and
                 not itm.data(0, self.ignoredRole))
@@ -1381,3 +1473,97 @@
             index = self.builtinsAssignmentList.indexOfTopLevelItem(itm)
             self.builtinsAssignmentList.takeTopLevelItem(index)
             del itm
+    
+    def __initCategoriesList(self, enabledCategories):
+        """
+        Private method to set the enabled status of the checker categories.
+        
+        @param enabledCategories comma separated list of enabled checker
+            categories
+        @type str
+        """
+        if enabledCategories:
+            enabledCategoriesList = [
+                c.strip() for c in enabledCategories.split(",")
+                if bool(c.strip())]
+        else:
+            enabledCategoriesList = list(
+                CodeStyleCheckerDialog.checkCategories.keys())
+        for row in range(self.categoriesList.count()):
+            itm = self.categoriesList.item(row)
+            if itm.data(Qt.UserRole) in enabledCategoriesList:
+                itm.setCheckState(Qt.Checked)
+            else:
+                itm.setCheckState(Qt.Unchecked)
+    
+    def __getCategories(self, enabled, asList=False):
+        """
+        Private method to get the enabled or disabled checker categories.
+        
+        @param enabled flag indicating to return enabled categories
+        @type bool
+        @param asList flag indicating to return the checker categories as a
+            Python list
+        @type bool
+        @return checker categories as a list or comma separated string
+        @rtype str or list of str
+        """
+        state = Qt.Checked if enabled else Qt.Unchecked
+        
+        checkerList = []
+        for row in range(self.categoriesList.count()):
+            itm = self.categoriesList.item(row)
+            if itm.checkState() == state:
+                checkerList.append(itm.data(Qt.UserRole))
+        if asList:
+            return checkerList
+        else:
+            return ", ".join(checkerList)
+    
+    def __assembleExcludeMessages(self):
+        """
+        Private method to assemble the list of excluded checks.
+        
+        @return list of excluded checks as a comma separated string.
+        @rtype str
+        """
+        excludeMessages = self.excludeMessagesEdit.text()
+        disabledCategories = self.__getCategories(False)
+        
+        if excludeMessages and disabledCategories:
+            return disabledCategories + "," + excludeMessages
+        elif disabledCategories:
+            return disabledCategories
+        elif excludeMessages:
+            return excludeMessages
+        else:
+            return ""
+    
+    def __cleanupData(self):
+        """
+        Private method to clean the loaded/entered data of redundant entries.
+        """
+        # Migrate single letter exclude messages to disabled checker categories
+        # and delete them from exlude messages
+        excludedMessages = [
+            m.strip()
+            for m in self.excludeMessagesEdit.text().split(",")
+            if bool(m)
+        ]
+        excludedMessageCategories = [
+            c for c in excludedMessages if len(c) == 1
+        ]
+        enabledCheckers = self.__getCategories(True, asList=True)
+        for category in excludedMessageCategories:
+            if category in enabledCheckers:
+                enabledCheckers.remove(category)
+            excludedMessages.remove(category)
+        
+        # Remove excluded messages of an already excluded category
+        disabledCheckers = self.__getCategories(False, asList=True)
+        for message in excludedMessages[:]:
+            if message[0] in disabledCheckers:
+                excludedMessages.remove(message)
+        
+        self.excludeMessagesEdit.setText(",".join(excludedMessages))
+        self.__initCategoriesList(",".join(enabledCheckers))

eric ide

mercurial