Tue, 10 Dec 2024 15:48:52 +0100
Updated copyright for 2025.
# -*- coding: utf-8 -*- # Copyright (c) 2015 - 2025 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the vulture plug-in. """ import os from PyQt6.QtCore import QObject, QTranslator, pyqtSignal from eric7 import Preferences from eric7.EricGui.EricAction import EricAction from eric7.EricWidgets.EricApplication import ericApp from eric7.SystemUtilities import PythonUtilities # Start-Of-Header __header__ = { "name": "Unused Code Checker Plug-in", "author": "Detlev Offenbach <detlev@die-offenbachs.de>", "autoactivate": True, "deactivateable": True, "version": "10.3.4", "className": "VulturePlugin", "packageName": "VultureChecker", "shortDescription": "Plug-in to detect unused code using the 'vulture' library", "longDescription": "Plug-in to detect unused code using the 'vulture' library.", "needsRestart": False, "hasCompiledForms": True, "pyqtApi": 2, } # End-Of-Header error = "" class VulturePlugin(QObject): """ Class documentation goes here. @signal analysisDone(str, dict) emitted when the code analysis has been completed for a file @signal error(str, str) emitted in case of an error @signal batchFinished() emitted when a style check batch is done """ analysisDone = pyqtSignal(str, dict) batchFinished = pyqtSignal() error = pyqtSignal(str, str) def __init__(self, ui): """ Constructor @param ui reference to the user interface object @type UserInterface """ super().__init__(ui) self.__ui = ui self.__initialize() self.backgroundService = ericApp().getObject("BackgroundService") path = os.path.join(os.path.dirname(__file__), __header__["packageName"]) self.backgroundService.serviceConnect( "vulture", "Python3", path, "VultureCheckerService", self.vultureCheckDone, onErrorCallback=self.serviceErrorPy3, onBatchDone=self.batchJobDone, ) self.queuedBatches = [] self.batchesFinished = True self.__translator = None self.__loadTranslator() def __serviceError(self, fn, msg): """ Private slot handling service errors. @param fn file name @type str @param msg message text @type str """ self.error.emit(fn, msg) def serviceErrorPy3(self, fx, lang, fn, msg): """ Public slot handling service errors for Python 3. @param fx service name @type str @param lang language @type str @param fn file name @type str @param msg message text @type str """ if fx in ["vulture", "batch_vulture"] and lang == "Python3": if fx == "vulture": self.__serviceError(fn, msg) else: self.__serviceError(self.tr("Python 3 batch job"), msg) self.batchJobDone(fx, lang) def batchJobDone(self, fx, lang): """ Public slot handling the completion of a batch job. @param fx service name @type str @param lang language @type str """ if fx in ["vulture", "batch_vulture"]: if lang in self.queuedBatches: self.queuedBatches.remove(lang) # prevent sending the signal multiple times if len(self.queuedBatches) == 0 and not self.batchesFinished: self.batchFinished.emit() self.batchesFinished = True def vultureCheckDone(self, filename, result): """ Public slot to dispatch the result. @param filename name of the file the results belong to @type str @param result result dictionary @type dict """ self.analysisDone.emit(filename, result) def __initialize(self): """ Private slot to (re)initialize the plug-in. """ self.__projectAct = None self.__projectVultureCheckerDialog = None def vultureCheck(self, lang, filename, source): """ Public method to prepare a vulture check for a Python project. @param lang language of the files or None to determine by internal algorithm @type str or None @param filename name of the file to analyze @type str @param source string containing the code @type str """ if lang is None: try: if PythonUtilities.isPythonSource(filename, source): lang = "Python3" except AttributeError: # backward compatibility for eric-ide < 24.8 if PythonUtilities.determinePythonVersion(filename, source) == 3: lang = "Python3" if lang == "Python3": self.backgroundService.enqueueRequest("vulture", lang, filename, [source]) def vultureCheckBatch(self, argumentsList): """ Public method to prepare a vulture check for a Python project using the batch mode. @param argumentsList list of arguments tuples with each tuple containing filename and source @type (str, str) """ data = { "Python3": [], } for filename, source in argumentsList: try: if PythonUtilities.isPythonSource(filename, source): data["Python3"].append((filename, source)) except AttributeError: # backward compatibility for eric-ide < 24.8 if PythonUtilities.determinePythonVersion(filename, source) == 3: data["Python3"].append((filename, source)) self.queuedBatches = [] if data["Python3"]: self.queuedBatches.append("Python3") self.backgroundService.enqueueRequest( "batch_vulture", "Python3", "", data["Python3"] ) self.batchesFinished = False def cancelVultureCheckBatch(self): """ Public method to cancel all batch jobs. """ self.backgroundService.requestCancel("batch_vulture", "Python3") def activate(self): """ Public method to activate this plug-in. @return tuple of None and activation status @rtype tuple of (None, bool) """ global error error = "" # clear previous error menu = ericApp().getObject("Project").getMenu("Checks") if menu: self.__projectAct = EricAction( self.tr("Check Unused Code"), self.tr("&Unused Code..."), 0, 0, self, "project_check_vulture", ) self.__projectAct.setStatusTip(self.tr("Check for unused code")) self.__projectAct.setWhatsThis( self.tr( """<b>Check Unused Code...</b>""" """<p>This checks a Python project for unused code.</p>""" ) ) self.__projectAct.triggered.connect(self.__projectVultureCheck) ericApp().getObject("Project").addEricActions([self.__projectAct]) menu.addAction(self.__projectAct) ericApp().getObject("Project").showMenu.connect(self.__projectShowMenu) ericApp().getObject("Project").projectClosed.connect(self.__projectClosed) return None, True def deactivate(self): """ Public method to deactivate this plug-in. """ ericApp().getObject("Project").showMenu.disconnect(self.__projectShowMenu) ericApp().getObject("Project").projectClosed.disconnect(self.__projectClosed) menu = ericApp().getObject("Project").getMenu("Checks") if menu is not None and self.__projectAct is not None: menu.removeAction(self.__projectAct) ericApp().getObject("Project").removeEricActions([self.__projectAct]) self.__initialize() def __loadTranslator(self): """ Private method to load the translation file. """ if self.__ui is not None: loc = self.__ui.getLocale() if loc and loc != "C": locale_dir = os.path.join( os.path.dirname(__file__), "VultureChecker", "i18n" ) translation = "vulture_{0}".format(loc) translator = QTranslator(None) loaded = translator.load(translation, locale_dir) if loaded: self.__translator = translator ericApp().installTranslator(self.__translator) else: print( "Warning: translation file '{0}' could not be" " loaded.".format(translation) ) print("Using default.") def __projectShowMenu( self, menuName, menu, # noqa: U100 ): """ Private slot called, when the the project menu or a submenu is about to be shown. @param menuName name of the menu to be shown @type str @param menu reference to the menu @type QMenu """ if menuName == "Check" and self.__projectAct is not None: self.__projectAct.setEnabled( ericApp().getObject("Project").getProjectLanguage() == "Python3" ) def __projectVultureCheck(self): """ Private slot used to check the project for unused code. """ project = ericApp().getObject("Project") project.saveAllScripts() ppath = project.getProjectPath() files = [ os.path.join(ppath, file_) for file_ in project.getSources() if file_.endswith(tuple(Preferences.getPython("Python3Extensions"))) ] if self.__projectVultureCheckerDialog is None: from VultureChecker.VultureCheckerDialog import ( # noqa: I101 VultureCheckerDialog, ) self.__projectVultureCheckerDialog = VultureCheckerDialog(self) self.__projectVultureCheckerDialog.show() self.__projectVultureCheckerDialog.prepare(files, project) def __projectClosed(self): """ Private slot to handle closing a project. """ if self.__projectVultureCheckerDialog: self.__projectVultureCheckerDialog.clear() def installDependencies(pipInstall): """ Function to install dependencies of this plug-in. @param pipInstall function to be called with a list of package names. @type function """ try: import vulture # __IGNORE_WARNING__ except ImportError: pipInstall(["vulture>=2.3"]) # # eflag: noqa = M801