diff -r 58860f9f3046 -r 6393ee6e7993 VultureChecker/VultureCheckerDialog.py --- a/VultureChecker/VultureCheckerDialog.py Mon Sep 19 18:06:19 2022 +0200 +++ b/VultureChecker/VultureCheckerDialog.py Mon Sep 19 18:11:49 2022 +0200 @@ -13,8 +13,13 @@ from PyQt6.QtCore import pyqtSlot, Qt, QTimer from PyQt6.QtWidgets import ( - QDialog, QDialogButtonBox, QAbstractButton, QHeaderView, QTreeWidgetItem, - QApplication, QMenu + QDialog, + QDialogButtonBox, + QAbstractButton, + QHeaderView, + QTreeWidgetItem, + QApplication, + QMenu, ) from .Ui_VultureCheckerDialog import Ui_VultureCheckerDialog @@ -29,11 +34,11 @@ """ Class to hold the name, type, confidence and location of defined code. """ - def __init__(self, name, typ, filename, firstLineno, lastLineno, - confidence): + + def __init__(self, name, typ, filename, firstLineno, lastLineno, confidence): """ Constructor - + @param name item name @type str @param typ item type @@ -59,13 +64,14 @@ """ Class implementing a dialog to show the vulture check results. """ + FilePathRole = Qt.ItemDataRole.UserRole + 1 TypeRole = Qt.ItemDataRole.UserRole + 2 - + def __init__(self, vultureService, parent=None): """ Constructor - + @param vultureService reference to the service @type VulturePlugin @param parent reference to the parent widget @@ -74,44 +80,43 @@ super().__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.WindowType.Window) - - self.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setEnabled(False) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).setDefault(True) - + + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) + self.resultList.headerItem().setText(self.resultList.columnCount(), "") - + self.__menu = QMenu(self) self.__whiteListAct = self.__menu.addAction( - self.tr("Add to Whitelist"), self.__whiteList) + self.tr("Add to Whitelist"), self.__whiteList + ) self.__menu.addSeparator() - self.__menu.addAction( - self.tr("Edit Whitelist"), self.__editWhiteList) + self.__menu.addAction(self.tr("Edit Whitelist"), self.__editWhiteList) self.__menu.addSeparator() self.__collapseAct = self.__menu.addAction( - self.tr("Collapse all"), self.__resultCollapse) + self.tr("Collapse all"), self.__resultCollapse + ) self.__expandAct = self.__menu.addAction( - self.tr("Expand all"), self.__resultExpand) - self.resultList.customContextMenuRequested.connect( - self.__showContextMenu) - + self.tr("Expand all"), self.__resultExpand + ) + self.resultList.customContextMenuRequested.connect(self.__showContextMenu) + self.vultureService = vultureService self.vultureService.analysisDone.connect(self.__processResult) self.vultureService.error.connect(self.__processError) self.vultureService.batchFinished.connect(self.__batchFinished) - + self.cancelled = False - + self.__project = ericApp().getObject("Project") self.__finished = True self.__errorItem = None self.__data = None self.__slotsAreUsed = True - + self.__fileList = [] self.filterFrame.setVisible(False) - + self.__translatedTypes = { "property": self.tr("Property"), "function": self.tr("Function"), @@ -121,33 +126,31 @@ "class": self.tr("Class"), "import": self.tr("Import"), } - + def __createErrorItem(self, filename, message): """ Private slot to create a new error item in the result list. - + @param filename name of the file @type str @param message error message @type str """ if self.__errorItem is None: - self.__errorItem = QTreeWidgetItem(self.resultList, [ - self.tr("Errors")]) + self.__errorItem = QTreeWidgetItem(self.resultList, [self.tr("Errors")]) self.__errorItem.setExpanded(True) self.__errorItem.setForeground(0, Qt.GlobalColor.red) - - msg = "{0} ({1})".format(self.__project.getRelativePath(filename), - message) + + msg = "{0} ({1})".format(self.__project.getRelativePath(filename), message) if not self.resultList.findItems(msg, Qt.MatchFlag.MatchExactly): itm = QTreeWidgetItem(self.__errorItem, [msg]) itm.setForeground(0, Qt.GlobalColor.red) itm.setFirstColumnSpanned(True) - + def prepare(self, fileList, project): """ Public method to prepare the dialog with a list of filenames. - + @param fileList list of filenames @type list of str @param project reference to the project object @@ -155,18 +158,14 @@ """ self.__fileList = fileList[:] self.__project = project - - self.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setEnabled(True) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).setEnabled(False) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setDefault(True) - + + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True) + self.filterFrame.setVisible(True) - - self.__data = self.__project.getData( - "CHECKERSPARMS", "Vulture") + + self.__data = self.__project.getData("CHECKERSPARMS", "Vulture") if self.__data is None: self.__data = {} if "ExcludeFiles" not in self.__data: @@ -188,13 +187,13 @@ self.__data["WhiteLists"]["method"] = [] if "import" not in self.__data["WhiteLists"]: self.__data["WhiteLists"]["import"] = [] - + self.excludeFilesEdit.setText(self.__data["ExcludeFiles"]) - + def start(self, fn): """ Public slot to start the code metrics determination. - + @param fn file or list of files or directory to show the code metrics for @type str or list of str @@ -202,25 +201,21 @@ self.cancelled = False self.__errorItem = None self.resultList.clear() - - self.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setEnabled(False) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).setEnabled(True) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).setDefault(True) + + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) QApplication.processEvents() - + self.__prepareResultLists() - + if isinstance(fn, list): self.files = fn elif os.path.isdir(fn): self.files = [] extensions = set(Preferences.getPython("Python3Extensions")) for ext in extensions: - self.files.extend( - Utilities.direntries(fn, True, '*{0}'.format(ext), 0)) + self.files.extend(Utilities.direntries(fn, True, "*{0}".format(ext), 0)) else: self.files = [fn] self.files.sort() @@ -228,16 +223,16 @@ for f in self.files[:]: if not os.path.exists(f): self.files.remove(f) - + if len(self.files) > 0: # disable updates of the list for speed self.resultList.setUpdatesEnabled(False) self.resultList.setSortingEnabled(False) - + self.checkProgress.setMaximum(len(self.files)) self.checkProgress.setVisible(len(self.files) > 1) QApplication.processEvents() - + # now go through all the files self.progress = 0 if len(self.files) == 1: @@ -246,11 +241,11 @@ else: self.__batch = True self.vultureCheckBatch() - + def vultureCheck(self): """ Public method to start a vulture check for one Python file. - + The results are reported to the __processResult slot. """ if not self.files: @@ -258,14 +253,14 @@ self.checkProgress.setValue(1) self.__finish() return - + self.filename = self.files.pop(0) self.checkProgress.setValue(self.progress) QApplication.processEvents() - + if self.cancelled: return - + try: self.source = Utilities.readEncodedFile(self.filename)[0] self.source = Utilities.normalizeCode(self.source) @@ -277,36 +272,35 @@ return self.__finished = False - self.vultureService.vultureCheck( - None, self.filename, self.source) + self.vultureService.vultureCheck(None, self.filename, self.source) def vultureCheckBatch(self): """ Public method to start a vulture check batch job. - + The results are reported to the __processResult slot. """ argumentsList = [] for progress, filename in enumerate(self.files, start=1): self.checkProgress.setValue(progress) QApplication.processEvents() - + try: source = Utilities.readEncodedFile(filename)[0] source = Utilities.normalizeCode(source) except (UnicodeError, OSError) as msg: self.__createErrorItem(filename, str(msg).rstrip()) continue - + argumentsList.append((filename, source)) - + # reset the progress bar to the checked files self.checkProgress.setValue(self.progress) QApplication.processEvents() - + self.__finished = False self.vultureService.vultureCheckBatch(argumentsList) - + def __batchFinished(self): """ Private slot handling the completion of a batch job. @@ -314,22 +308,22 @@ self.checkProgress.setMaximum(1) self.checkProgress.setValue(1) self.__finish() - + def __processError(self, fn, msg): """ Private slot to process an error indication from the service. - + @param fn filename of the file @type str @param msg error message @type str """ self.__createErrorItem(fn, msg) - + def __processResult(self, fn, result): """ Private slot called after performing a vulture analysis on one file. - + @param fn filename of the file @type str @param result result dict @@ -337,25 +331,25 @@ """ if self.__finished: return - + # Check if it's the requested file, otherwise ignore signal if not # in batch mode if not self.__batch and fn != self.filename: return - + if "error" in result: self.__createErrorItem(fn, result["error"]) else: self.__storeResult(result) - + self.progress += 1 - + self.checkProgress.setValue(self.progress) QApplication.processEvents() - + if not self.__batch: self.vultureCheck() - + def __finish(self): """ Private slot called when the action finished or the user pressed the @@ -363,90 +357,89 @@ """ if not self.__finished: self.__finished = True - + if not self.cancelled: self.__createResultItems() - + # reenable updates of the list self.resultList.setSortingEnabled(True) self.resultList.sortItems(0, Qt.SortOrder.AscendingOrder) self.resultList.setUpdatesEnabled(True) - + self.cancelled = True - self.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setEnabled(True) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel).setEnabled(False) - self.buttonBox.button( - QDialogButtonBox.StandardButton.Close).setDefault(True) - + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled( + True + ) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled( + False + ) + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault( + True + ) + self.resultList.header().resizeSections( - QHeaderView.ResizeMode.ResizeToContents) + QHeaderView.ResizeMode.ResizeToContents + ) self.resultList.header().setStretchLastSection(True) self.resultList.header().setSectionResizeMode( - QHeaderView.ResizeMode.Interactive) - + QHeaderView.ResizeMode.Interactive + ) + self.checkProgress.setVisible(False) - + if self.resultList.topLevelItemCount() == 0: - itm = QTreeWidgetItem(self.resultList, - [self.tr("No unused code found.")]) + itm = QTreeWidgetItem( + self.resultList, [self.tr("No unused code found.")] + ) itm.setFirstColumnSpanned(True) - + @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Private slot called by a button of the button box clicked. - + @param button button that was clicked @type QAbstractButton """ - if button == self.buttonBox.button( - QDialogButtonBox.StandardButton.Close - ): + if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close): self.close() - elif button == self.buttonBox.button( - QDialogButtonBox.StandardButton.Cancel - ): + elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel): self.cancelled = True if self.__batch: self.vultureService.cancelVultureCheckBatch() QTimer.singleShot(1000, self.__finish) else: self.__finish() - + @pyqtSlot() def on_startButton_clicked(self): """ Private slot to start a code metrics run. """ fileList = self.__fileList[:] - + filterString = self.excludeFilesEdit.text() if filterString != self.__data["ExcludeFiles"]: self.__data["ExcludeFiles"] = filterString - self.__project.setData( - "CHECKERSPARMS", "Vulture", self.__data) - filterList = [f.strip() for f in filterString.split(",") - if f.strip()] + self.__project.setData("CHECKERSPARMS", "Vulture", self.__data) + filterList = [f.strip() for f in filterString.split(",") if f.strip()] if filterList: for fileFilter in filterList: - fileList = [f for f in fileList - if not fnmatch.fnmatch(f, fileFilter)] - + fileList = [f for f in fileList if not fnmatch.fnmatch(f, fileFilter)] + self.start(fileList) - + def clear(self): """ Public method to clear all results. """ self.resultList.clear() - + @pyqtSlot(QTreeWidgetItem, int) def on_resultList_itemActivated(self, item, column): """ Private slot to handle the activation of a result item. - + @param item reference to the activated item @type QTreeWidgetItem @param column column the item was activated in @@ -461,7 +454,7 @@ if filename: vm = ericApp().getObject("ViewManager") vm.openSourceFile(filename, lineno) - + def __prepareResultLists(self): """ Private method to prepare the result lists. @@ -473,46 +466,67 @@ self.__unusedImports = [] self.__unusedProps = [] self.__unusedVars = [] - + def __storeResult(self, result): """ Private method to store the result of an analysis. - + @param result result dictionary @type dict """ - self.__unusedAttrs.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["UnusedAttributes"]])) - self.__unusedClasses.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["UnusedClasses"]])) - self.__unusedFuncs.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["UnusedFunctions"]])) - self.__unusedMethods.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["UnusedMethods"]])) - self.__unusedImports.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["UnusedImports"]])) - self.__unusedProps.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["UnusedProperties"]])) - self.__unusedVars.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["UnusedVariables"]])) - + self.__unusedAttrs.extend( + self.__filteredList( + [self.__dict2Item(d) for d in result["UnusedAttributes"]] + ) + ) + self.__unusedClasses.extend( + self.__filteredList([self.__dict2Item(d) for d in result["UnusedClasses"]]) + ) + self.__unusedFuncs.extend( + self.__filteredList( + [self.__dict2Item(d) for d in result["UnusedFunctions"]] + ) + ) + self.__unusedMethods.extend( + self.__filteredList([self.__dict2Item(d) for d in result["UnusedMethods"]]) + ) + self.__unusedImports.extend( + self.__filteredList([self.__dict2Item(d) for d in result["UnusedImports"]]) + ) + self.__unusedProps.extend( + self.__filteredList( + [self.__dict2Item(d) for d in result["UnusedProperties"]] + ) + ) + self.__unusedVars.extend( + self.__filteredList( + [self.__dict2Item(d) for d in result["UnusedVariables"]] + ) + ) + def __dict2Item(self, d): """ Private method to convert an item dictionary to a vulture item. - + @param d item dictionary @type dict @return vulture item @rtype VultureItem """ - return VultureItem(d["name"], d["type"], d["file"], d["first_line"], - d["last_line"], confidence=d["confidence"]) - + return VultureItem( + d["name"], + d["type"], + d["file"], + d["first_line"], + d["last_line"], + confidence=d["confidence"], + ) + def __filteredList(self, itemList): """ Private method to filter a list against the whitelist patterns returning items not matching the whitelist. - + @param itemList list of items to be filtered @type list of VultureItem @return list of filtered items @@ -520,14 +534,17 @@ """ filteredList = itemList for pattern in self.__data["WhiteLists"]["__patterns__"]: - filteredList = [item for item in filteredList - if not fnmatch.fnmatchcase(item.name, pattern)] - return filteredList # __IGNORE_WARNING_M834__ - + filteredList = [ + item + for item in filteredList + if not fnmatch.fnmatchcase(item.name, pattern) + ] + return filteredList # __IGNORE_WARNING_M834__ + def __filterUnusedItems(self, unused, whitelistName): """ Private method to get a list of unused items. - + @param unused list of unused items @type list of VultureItem @param whitelistName name of the whitelist to use as a filter @@ -536,14 +553,15 @@ @rtype list of VultureItem """ return [ - item for item in set(unused) + item + for item in set(unused) if item.name not in self.__data["WhiteLists"][whitelistName] ] def __filterUnusedFunctions(self): """ Private method to get the list of unused functions. - + @return list of unused functions @rtype list of VultureItem """ @@ -552,7 +570,7 @@ def __filterUnusedMethods(self): """ Private method to get the list of unused methods. - + @return list of unused methods @rtype list of VultureItem """ @@ -561,7 +579,7 @@ def __filterUnusedClasses(self): """ Private method to get the list of unused classes. - + @return list of unused classes @rtype list of VultureItem """ @@ -570,16 +588,16 @@ def __filterUnusedImports(self): """ Private method to get a list of unused imports. - + @return list of unused imports @rtype list of VultureItem """ return self.__filterUnusedItems(self.__unusedImports, "import") - + def __filterUnusedProperties(self): """ Private method to get the list of unused properties. - + @return list of unused properties @rtype list of VultureItem """ @@ -588,7 +606,7 @@ def __filterUnusedVariables(self): """ Private method to get the list of unused variables. - + @return list of unused variables @rtype list of VultureItem """ @@ -597,36 +615,38 @@ def __filterUnusedAttributes(self): """ Private method to get the list of unused attributes. - + @return list of unused attributes @rtype list of VultureItem """ return self.__filterUnusedItems(self.__unusedAttrs, "attribute") - + def __createResultItems(self): """ Private method to populate the list with the analysis result. """ lastFileItem = None lastFileName = "" - items = (self.__filterUnusedFunctions() + - self.__filterUnusedMethods() + - self.__filterUnusedClasses() + - self.__filterUnusedImports() + - self.__filterUnusedProperties() + - self.__filterUnusedVariables() + - self.__filterUnusedAttributes()) + items = ( + self.__filterUnusedFunctions() + + self.__filterUnusedMethods() + + self.__filterUnusedClasses() + + self.__filterUnusedImports() + + self.__filterUnusedProperties() + + self.__filterUnusedVariables() + + self.__filterUnusedAttributes() + ) for item in sorted(items, key=lambda item: item.filename): if lastFileItem is None or lastFileName != item.filename: lastFileItem = self.__createFileItem(item.filename) lastFileName = item.filename - + self.__createResultItem(lastFileItem, item) - + def __createResultItem(self, parent, item): """ Private method to create a result item. - + @param parent reference to the parent item @type QTreeWidgetItem @param item reference to the item @@ -636,81 +656,87 @@ translatedType = self.__translatedTypes[item.typ] except KeyError: translatedType = item.typ - itm = QTreeWidgetItem(parent, [ - "{0:6d}".format(item.first_lineno), item.name, - "{0:3d}%".format(item.confidence), translatedType]) + itm = QTreeWidgetItem( + parent, + [ + "{0:6d}".format(item.first_lineno), + item.name, + "{0:3d}%".format(item.confidence), + translatedType, + ], + ) itm.setData(0, self.FilePathRole, item.filename) itm.setData(0, self.TypeRole, item.typ) - itm.setTextAlignment(0, Qt.AlignmentFlag.AlignRight) # line no - itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight) # confidence - + itm.setTextAlignment(0, Qt.AlignmentFlag.AlignRight) # line no + itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight) # confidence + def __createFileItem(self, filename): """ Private method to create a file item. - + @param filename file name for the item @type str @return reference to the created item @rtype QTreeWidgetItem """ - itm = QTreeWidgetItem(self.resultList, [ - self.__project.getRelativePath(filename)]) + itm = QTreeWidgetItem( + self.resultList, [self.__project.getRelativePath(filename)] + ) itm.setData(0, self.FilePathRole, filename) itm.setExpanded(True) itm.setFirstColumnSpanned(True) - + return itm - + def __showContextMenu(self, coord): """ Private slot to show the context menu of the listview. - + @param coord the position of the mouse pointer @type QPoint """ topLevelPresent = self.resultList.topLevelItemCount() > 0 self.__collapseAct.setEnabled(topLevelPresent) self.__expandAct.setEnabled(topLevelPresent) - - self.__whiteListAct.setEnabled( - len(self.__getSelectedNonFileItems()) != 0) - + + self.__whiteListAct.setEnabled(len(self.__getSelectedNonFileItems()) != 0) + self.__menu.popup(self.resultList.mapToGlobal(coord)) - + def __resultCollapse(self): """ Private slot to collapse all entries of the resultlist. """ for index in range(self.resultList.topLevelItemCount()): self.resultList.topLevelItem(index).setExpanded(False) - + def __resultExpand(self): """ Private slot to expand all entries of the resultlist. """ for index in range(self.resultList.topLevelItemCount()): self.resultList.topLevelItem(index).setExpanded(True) - + def __getSelectedNonFileItems(self): """ Private method to get a list of selected non file items. - + @return list of selected non file items @rtype list of QTreeWidgetItem """ - return [i for i in self.resultList.selectedItems() - if i.parent() is not None] - + return [i for i in self.resultList.selectedItems() if i.parent() is not None] + def __editWhiteList(self): """ Private slot to edit the whitelist. """ from .EditWhiteListDialog import EditWhiteListDialog + dlg = EditWhiteListDialog(self.__data["WhiteLists"]) if dlg.exec() == QDialog.DialogCode.Accepted: whitelists = dlg.getWhiteLists() self.__storeWhiteLists(whitelists) - + def __whiteList(self): """ Private slot to add entries to the whitelist. @@ -727,14 +753,15 @@ del itm if pitm.childCount() == 0: self.resultList.takeTopLevelItem( - self.resultList.indexOfTopLevelItem(pitm)) + self.resultList.indexOfTopLevelItem(pitm) + ) del pitm self.__storeWhiteLists(whitelists) - + def __storeWhiteLists(self, whitelists): """ Private method to store the new whitelists, if they have changed. - + @param whitelists dictionary of lists of whitelisted names @type dict of list of str """ @@ -745,6 +772,6 @@ if sorted(whitelist) != sorted(self.__data["WhiteLists"][key]): self.__data["WhiteLists"][key] = whitelist[:] changed = True - + if changed: self.__project.setData("CHECKERSPARMS", "Vulture", self.__data)