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