Tue, 07 Nov 2023 15:17:48 +0100
Implemented the first iteration of the pyright typing checker interface.
# -*- coding: utf-8 -*- # Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the pyright plug-in. """ import contextlib import os from PyQt6.QtCore import QObject, QTranslator, pyqtSlot from eric7 import Preferences from eric7.EricGui.EricAction import EricAction from eric7.EricWidgets.EricApplication import ericApp from eric7.Project.ProjectBrowserModel import ProjectBrowserFileItem # Start-Of-Header __header__ = { "name": "Python Typing Checker Plug-in", "author": "Detlev Offenbach <detlev@die-offenbachs.de>", "autoactivate": True, "deactivateable": True, "version": "10.0.0", "className": "PyrightPlugin", "packageName": "PyrightChecker", "shortDescription": "Plug-in to check Python sources for typing issues.", "longDescription": ("""Plug-in to check Python sources for typing issues."""), "needsRestart": False, "pyqtApi": 2, } # End-Of-Header error = "" # noqa: U200 def prepareUninstall(): """ Module function to prepare for an uninstallation. """ Preferences.Prefs.settings.remove(PyrightPlugin.PreferencesKey) class PyrightPlugin(QObject): """ Class documentation goes here. """ PreferencesKey = "Pyright" def __init__(self, ui): """ Constructor @param ui reference to the user interface object @type UI.UserInterface """ super().__init__(ui) self.__ui = ui self.__initialize() def __initialize(self): """ Private slot to (re)initialize the plug-in. """ self.__projectAct = None self.__projectPyrightCheckerDialog = None self.__projectBrowserAct = None self.__projectBrowserMenu = None self.__projectBrowserPyrightCheckerDialog = None self.__editors = [] self.__editorAct = None self.__editorPyrightCheckerDialog = None def activate(self): """ Public method to activate this plug-in. @return tuple of None and activation status @rtype bool """ global error error = "" # clear previous error menu = ericApp().getObject("Project").getMenu("Checks") if menu: self.__projectAct = EricAction( self.tr("Static Type Check"), self.tr("Static &Typing..."), 0, 0, self, "project_check_pyright", ) self.__projectAct.setStatusTip(self.tr("Check for typing issues")) self.__projectAct.setWhatsThis( self.tr( """<b>Static Type Check...</b>""" """<p>This checks a Python project for static typing issues.</p>""" ) ) self.__projectAct.triggered.connect(self.__projectPyrightCheck) ericApp().getObject("Project").addEricActions([self.__projectAct]) menu.addAction(self.__projectAct) self.__editorAct = EricAction( self.tr("Static Type Check"), self.tr("Static &Typing..."), 0, 0, self, "" ) self.__editorAct.setWhatsThis( self.tr( """<b>Static Type Check...</b>""" """<p>This checks a Python file for static typing issues.</p>""" ) ) self.__editorAct.triggered.connect(self.__editorPyrightCheck) ericApp().getObject("Project").showMenu.connect(self.__projectShowMenu) ericApp().getObject("ProjectBrowser").getProjectBrowser( "sources" ).showMenu.connect(self.__projectBrowserShowMenu) ericApp().getObject("ViewManager").editorOpenedEd.connect(self.__editorOpened) ericApp().getObject("ViewManager").editorClosedEd.connect(self.__editorClosed) for editor in ericApp().getObject("ViewManager").getOpenEditors(): self.__editorOpened(editor) return None, True def deactivate(self): """ Public method to deactivate this plug-in. """ ericApp().getObject("Project").showMenu.disconnect(self.__projectShowMenu) ericApp().getObject("ProjectBrowser").getProjectBrowser( "sources" ).showMenu.disconnect(self.__projectBrowserShowMenu) 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]) if self.__projectBrowserMenu and self.__projectBrowserAct: self.__projectBrowserMenu.removeAction(self.__projectBrowserAct) for editor in self.__editors: editor.showMenu.disconnect(self.__editorShowMenu) menu = editor.getMenu("Checks") if menu is not None: menu.removeAction(self.__editorAct) 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__), "PyrightChecker", "i18n" ) translation = "pyright_{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 __projectBrowserShowMenu(self, menuName, menu): """ Private slot called, when the the project browser 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 == "Checks" and ericApp().getObject("Project").getProjectLanguage() == "Python3" ): self.__projectBrowserMenu = menu if self.__projectBrowserAct is None: self.__projectBrowserAct = EricAction( self.tr("Static Type Check"), self.tr("Static &Typing..."), 0, 0, self, "", ) self.__projectBrowserAct.setWhatsThis( self.tr( """<b>Static Type Check...</b>""" """<p>This checks Python files for static typing issues.</p>""" ) ) self.__projectBrowserAct.triggered.connect( self.__projectBrowserPyrightCheck ) if self.__projectBrowserAct not in menu.actions(): menu.addAction(self.__projectBrowserAct) @pyqtSlot() def __projectPyrightCheck(self): """ Private slot used to check the project for static typing issues. """ from PyrightChecker.PyrightCheckerDialog import PyrightCheckerDialog project = ericApp().getObject("Project") project.saveAllScripts() if self.__projectPyrightCheckerDialog is None: self.__projectPyrightCheckerDialog = PyrightCheckerDialog(self) self.__projectPyrightCheckerDialog.show() self.__projectPyrightCheckerDialog.prepare(project) @pyqtSlot() def __projectBrowserPyrightCheck(self): """ Private method to handle the static typing check context menu action of the project sources browser. """ from PyrightChecker.PyrightCheckerDialog import PyrightCheckerDialog browser = ericApp().getObject("ProjectBrowser").getProjectBrowser("sources") if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1: fn = [] for itm in browser.getSelectedItems([ProjectBrowserFileItem]): fn.append(itm.fileName()) else: itm = browser.model().item(browser.currentIndex()) try: fn = [itm.fileName()] except AttributeError: fn = [itm.dirName()] self.__projectBrowserPyrightCheckerDialog = PyrightCheckerDialog(self) self.__projectBrowserPyrightCheckerDialog.show() self.__projectBrowserPyrightCheckerDialog.start(fn, save=True) def __editorOpened(self, editor): """ Private slot called, when a new editor was opened. @param editor reference to the new editor @type QScintilla.Editor """ menu = editor.getMenu("Checks") if menu is not None: menu.addAction(self.__editorAct) editor.showMenu.connect(self.__editorShowMenu) self.__editors.append(editor) def __editorClosed(self, editor): """ Private slot called, when an editor was closed. @param editor reference to the editor @type QScintilla.Editor """ with contextlib.suppress(ValueError): self.__editors.remove(editor) def __editorShowMenu(self, menuName, menu, editor): """ Private slot called, when the the editor context 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 @param editor reference to the editor @type QScintilla.Editor """ if menuName == "Checks": if self.__editorAct not in menu.actions(): menu.addAction(self.__editorAct) self.__editorAct.setEnabled(editor.isPyFile()) def __editorPyrightCheck(self): """ Private slot to handle the code style check context menu action of the editors. """ from PyrightChecker.PyrightCheckerDialog import PyrightCheckerDialog editor = ericApp().getObject("ViewManager").activeWindow() if ( editor is not None and editor.checkDirty() and editor.getFileName() is not None ): self.__editorPyrightCheckerDialog = PyrightCheckerDialog(self) self.__editorPyrightCheckerDialog.show() self.__editorPyrightCheckerDialog.start([editor.getFileName()], save=True) 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 pyright # __IGNORE_WARNING__ except ImportError: pipInstall(["pyright"]) try: import tomlkit # __IGNORE_WARNING__ except ImportError: pipInstall(["tomlkit"]) # # eflag: noqa = M801