Sun, 25 Apr 2021 16:13:53 +0200
Upgraded embedded vulture library to version 2.3.0 (no eric patches for slot support needed anymore).
diff -r d7a6b7ea640d -r 3c2922b45a9f ChangeLog --- a/ChangeLog Wed Dec 30 11:02:01 2020 +0100 +++ b/ChangeLog Sun Apr 25 16:13:53 2021 +0200 @@ -1,5 +1,9 @@ ChangeLog --------- +Version 4.0.0: +- upgraded embedded vulture library to version 2.3.0 + (no eric patches for slot support needed anymore) + Version 3.1.1: - bug fixes
diff -r d7a6b7ea640d -r 3c2922b45a9f PluginCheckerVulture.epj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PluginCheckerVulture.epj Sun Apr 25 16:13:53 2021 +0200 @@ -0,0 +1,299 @@ +{ + "header": { + "comment": "eric project file for project PluginCheckerVulture", + "copyright": "Copyright (C) 2021 Detlev Offenbach, detlev@die-offenbachs.de" + }, + "project": { + "AUTHOR": "Detlev Offenbach", + "CHECKERSPARMS": { + "Pep8Checker": { + "AnnotationsChecker": { + "AllowUntypedDefs": false, + "AllowUntypedNested": false, + "DispatchDecorators": [ + "singledispatch", + "singledispatchmethod" + ], + "MaximumComplexity": 3, + "MaximumLength": 7, + "MinimumCoverage": 75, + "MypyInitReturn": false, + "OverloadDecorators": [ + "overload" + ], + "SuppressDummyArgs": false, + "SuppressNoneReturning": true + }, + "BlankLines": [ + 2, + 1 + ], + "BuiltinsChecker": { + "bytes": [ + "unicode" + ], + "chr": [ + "unichr" + ], + "str": [ + "unicode" + ] + }, + "CommentedCodeChecker": { + "Aggressive": false, + "WhiteList": [ + "pylint", + "pyright", + "noqa", + "type:\\s*ignore", + "fmt:\\s*(on|off)", + "TODO", + "FIXME", + "WARNING", + "NOTE", + "TEST", + "DOCU", + "XXX", + "- " + ] + }, + "CopyrightAuthor": "", + "CopyrightMinFileSize": 0, + "DocstringType": "eric", + "EnabledCheckerCategories": "C, D, E, M, N, S, Y, W", + "ExcludeFiles": "*/Ui_*.py, */*_rc.py, */vulture/*", + "ExcludeMessages": "C101,E265,E266,E305,E402,M201,M301,M302,M303,M304,M305,M306,M307,M308,M311,M312,M313,M314,M315,M321,M701,M702,M811,M834,N802,N803,N807,N808,N821,W293,W504,Y119,Y401,Y402", + "FixCodes": "", + "FixIssues": false, + "FutureChecker": "", + "HangClosing": false, + "IncludeMessages": "", + "LineComplexity": 25, + "LineComplexityScore": 10, + "MaxCodeComplexity": 10, + "MaxDocLineLength": 79, + "MaxLineLength": 79, + "NoFixCodes": "E501", + "RepeatMessages": true, + "SecurityChecker": { + "CheckTypedException": false, + "HardcodedTmpDirectories": [ + "/tmp", + "/var/tmp", + "/dev/shm", + "~/tmp" + ], + "InsecureHashes": [ + "md4", + "md5", + "sha", + "sha1" + ], + "InsecureSslProtocolVersions": [ + "PROTOCOL_SSLv2", + "SSLv2_METHOD", + "SSLv23_METHOD", + "PROTOCOL_SSLv3", + "PROTOCOL_TLSv1", + "SSLv3_METHOD", + "TLSv1_METHOD" + ], + "WeakKeySizeDsaHigh": "1024", + "WeakKeySizeDsaMedium": "2048", + "WeakKeySizeEcHigh": "160", + "WeakKeySizeEcMedium": "224", + "WeakKeySizeRsaHigh": "1024", + "WeakKeySizeRsaMedium": "2048" + }, + "ShowIgnored": false, + "ValidEncodings": "latin-1, utf-8" + }, + "Vulture": { + "ExcludeFiles": "", + "SlotsAreUsed": true, + "WhiteLists": { + "__patterns__": [ + "on_*", + "visit_*" + ], + "attribute": [], + "class": [], + "function": [], + "import": [], + "property": [], + "slot": [], + "variable": [] + } + } + }, + "DESCRIPTION": "Plug-in to detect unused code using the vulture library.", + "DOCSTRING": "", + "DOCUMENTATIONPARMS": { + "ERIC4DOC": { + "cssFile": "%PYTHON%/eric6/CSSs/default.css", + "ignoreDirectories": [ + ".hg", + ".ropeproject", + "_ropeproject", + ".eric6project", + "_eric6project", + "vulture" + ], + "ignoreFilePatterns": [ + "Ui_*.py" + ], + "outputDirectory": "VultureChecker/Documentation/source", + "qtHelpEnabled": false, + "useRecursion": true + } + }, + "EMAIL": "detlev@die-offenbachs.de", + "EOL": 1, + "FILETYPES": { + "*.idl": "INTERFACES", + "*.py": "SOURCES", + "*.py3": "SOURCES", + "*.pyw": "SOURCES", + "*.pyw3": "SOURCES", + "*.qm": "TRANSLATIONS", + "*.qrc": "RESOURCES", + "*.ts": "TRANSLATIONS", + "*.ui": "FORMS", + "Ui_*.py": "__IGNORE__" + }, + "FORMS": [ + "VultureChecker/EditWhiteListDialog.ui", + "VultureChecker/VultureCheckerDialog.ui" + ], + "HASH": "ba39fb6c6b6bbf35870f5dac20e42e8e562ca22b", + "IDLPARAMS": { + "DefinedNames": [], + "IncludeDirs": [], + "UndefinedNames": [] + }, + "INTERFACES": [], + "LEXERASSOCS": {}, + "MAINSCRIPT": "PluginVulture.py", + "MAKEPARAMS": { + "MakeEnabled": false, + "MakeExecutable": "", + "MakeFile": "", + "MakeParameters": "", + "MakeTarget": "", + "MakeTestOnly": true + }, + "MIXEDLANGUAGE": false, + "OTHERS": [ + ".hgignore", + "ChangeLog", + "PKGLIST", + "PluginCheckerVulture.e4p", + "PluginVulture.zip", + "VultureChecker/Documentation/LICENSE.GPL3", + "VultureChecker/Documentation/source", + "PluginCheckerVulture.epj", + "VultureChecker/vulture/LICENSE.txt" + ], + "OTHERTOOLSPARMS": {}, + "PACKAGERSPARMS": {}, + "PROGLANGUAGE": "Python3", + "PROJECTTYPE": "E6Plugin", + "PROJECTTYPESPECIFICDATA": {}, + "PROTOCOLS": [], + "RCCPARAMS": { + "CompressLevel": 0, + "CompressionDisable": false, + "CompressionThreshold": 70, + "PathPrefix": "" + }, + "RESOURCES": [], + "SOURCES": [ + "PluginVulture.py", + "VultureChecker/EditWhiteListDialog.py", + "VultureChecker/VultureCheckerDialog.py", + "VultureChecker/VultureCheckerService.py", + "VultureChecker/__init__.py", + "VultureChecker/vulture/__init__.py", + "VultureChecker/vulture/__main__.py", + "VultureChecker/vulture/core.py", + "VultureChecker/vulture/lines.py", + "VultureChecker/vulture/utils.py", + "VultureChecker/vulture/whitelists/argparse_whitelist.py", + "VultureChecker/vulture/whitelists/ast_whitelist.py", + "VultureChecker/vulture/whitelists/collections_whitelist.py", + "VultureChecker/vulture/whitelists/sys_whitelist.py", + "VultureChecker/vulture/whitelists/threading_whitelist.py", + "VultureChecker/vulture/whitelists/unittest_whitelist.py", + "VultureChecker/vulture/whitelists/whitelist_utils.py", + "__init__.py", + "VultureChecker/vulture/noqa.py", + "VultureChecker/vulture/config.py", + "VultureChecker/vulture/version.py", + "VultureChecker/vulture/whitelists/logging_whitelist.py", + "VultureChecker/vulture/whitelists/string_whitelist.py", + "VultureChecker/vulture/whitelists/ctypes_whitelist.py" + ], + "SPELLEXCLUDES": "", + "SPELLLANGUAGE": "en_US", + "SPELLWORDS": "", + "TRANSLATIONEXCEPTIONS": [], + "TRANSLATIONPATTERN": "VultureChecker/i18n/vulture_%language%.ts", + "TRANSLATIONS": [ + "VultureChecker/i18n/vulture_de.qm", + "VultureChecker/i18n/vulture_de.ts", + "VultureChecker/i18n/vulture_en.qm", + "VultureChecker/i18n/vulture_en.ts", + "VultureChecker/i18n/vulture_es.qm", + "VultureChecker/i18n/vulture_es.ts", + "VultureChecker/i18n/vulture_ru.qm", + "VultureChecker/i18n/vulture_ru.ts" + ], + "TRANSLATIONSBINPATH": "", + "UICPARAMS": { + "Package": "", + "PackagesRoot": "", + "RcSuffix": "" + }, + "VCS": "Mercurial", + "VCSOPTIONS": { + "add": [ + "" + ], + "checkout": [ + "" + ], + "commit": [ + "" + ], + "diff": [ + "" + ], + "export": [ + "" + ], + "global": [ + "" + ], + "history": [ + "" + ], + "log": [ + "" + ], + "remove": [ + "" + ], + "status": [ + "" + ], + "tag": [ + "" + ], + "update": [ + "" + ] + }, + "VCSOTHERDATA": {}, + "VERSION": "0.x" + } +} \ No newline at end of file
diff -r d7a6b7ea640d -r 3c2922b45a9f PluginVulture.py --- a/PluginVulture.py Wed Dec 30 11:02:01 2020 +0100 +++ b/PluginVulture.py Sun Apr 25 16:13:53 2021 +0200 @@ -22,7 +22,7 @@ author = "Detlev Offenbach <detlev@die-offenbachs.de>" autoactivate = True deactivateable = True -version = "3.1.1" +version = "4.0.0" className = "VulturePlugin" packageName = "VultureChecker" shortDescription = "Plug-in to detect unused code using the vulture library" @@ -55,7 +55,7 @@ @param ui reference to the user interface object (UI.UserInterface) """ - super(VulturePlugin, self).__init__(ui) + super().__init__(ui) self.__ui = ui self.__initialize() @@ -234,11 +234,10 @@ self.__projectClosed) menu = e5App().getObject("Project").getMenu("Checks") - if menu: - if self.__projectAct is not None: - menu.removeAction(self.__projectAct) - e5App().getObject("Project").removeE5Actions( - [self.__projectAct]) + if menu is not None and self.__projectAct is not None: + menu.removeAction(self.__projectAct) + e5App().getObject("Project").removeE5Actions( + [self.__projectAct]) self.__initialize() @@ -272,12 +271,11 @@ @param menu reference to the menu @type QMenu """ - if menuName == "Check": - if self.__projectAct is not None: - self.__projectAct.setEnabled( - e5App().getObject("Project") - .getProjectLanguage() == "Python3" - ) + if menuName == "Check" and self.__projectAct is not None: + self.__projectAct.setEnabled( + e5App().getObject("Project") + .getProjectLanguage() == "Python3" + ) def __projectVultureCheck(self): """
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/Documentation/source/Plugin_Checker_Vulture.PluginVulture.html --- a/VultureChecker/Documentation/source/Plugin_Checker_Vulture.PluginVulture.html Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/Documentation/source/Plugin_Checker_Vulture.PluginVulture.html Sun Apr 25 16:13:53 2021 +0200 @@ -239,7 +239,7 @@ Public method to activate this plug-in. </p> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> tuple of None and activation status (boolean) </dd>
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/Documentation/source/Plugin_Checker_Vulture.VultureChecker.EditWhiteListDialog.html --- a/VultureChecker/Documentation/source/Plugin_Checker_Vulture.VultureChecker.EditWhiteListDialog.html Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/Documentation/source/Plugin_Checker_Vulture.VultureChecker.EditWhiteListDialog.html Sun Apr 25 16:13:53 2021 +0200 @@ -113,6 +113,10 @@ <td>Private slot handling the selection of tab.</td> </tr> <tr> +<td><a href="#EditWhiteListDialog.on_methodsList_itemSelectionChanged">on_methodsList_itemSelectionChanged</a></td> +<td>Private slot to react upon a change of selection in the methods list.</td> +</tr> +<tr> <td><a href="#EditWhiteListDialog.on_patternsList_itemSelectionChanged">on_patternsList_itemSelectionChanged</a></td> <td>Private slot to react upon a change of selection in the patterns list.</td> </tr> @@ -129,10 +133,6 @@ <td>Private slot to remove the selected entries from the current list.</td> </tr> <tr> -<td><a href="#EditWhiteListDialog.on_slotsList_itemSelectionChanged">on_slotsList_itemSelectionChanged</a></td> -<td>Private slot to react upon a change of selection in the slots list.</td> -</tr> -<tr> <td><a href="#EditWhiteListDialog.on_variablesList_itemSelectionChanged">on_variablesList_itemSelectionChanged</a></td> <td>Private slot to react upon a change of selection in the variables list.</td> </tr> @@ -176,7 +176,7 @@ </dd> </dl> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> whitelisted names </dd> @@ -202,7 +202,7 @@ </dd> </dl> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> flag indicating a wildcard pattern </dd> @@ -228,7 +228,7 @@ Public methods to retrieve the various whitelists. </p> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> dictionary containing the whitelists </dd> @@ -289,6 +289,13 @@ index of the selected tab </dd> </dl> +<a NAME="EditWhiteListDialog.on_methodsList_itemSelectionChanged" ID="EditWhiteListDialog.on_methodsList_itemSelectionChanged"></a> +<h4>EditWhiteListDialog.on_methodsList_itemSelectionChanged</h4> +<b>on_methodsList_itemSelectionChanged</b>(<i></i>) + +<p> + Private slot to react upon a change of selection in the methods list. +</p> <a NAME="EditWhiteListDialog.on_patternsList_itemSelectionChanged" ID="EditWhiteListDialog.on_patternsList_itemSelectionChanged"></a> <h4>EditWhiteListDialog.on_patternsList_itemSelectionChanged</h4> <b>on_patternsList_itemSelectionChanged</b>(<i></i>) @@ -318,13 +325,6 @@ <p> Private slot to remove the selected entries from the current list. </p> -<a NAME="EditWhiteListDialog.on_slotsList_itemSelectionChanged" ID="EditWhiteListDialog.on_slotsList_itemSelectionChanged"></a> -<h4>EditWhiteListDialog.on_slotsList_itemSelectionChanged</h4> -<b>on_slotsList_itemSelectionChanged</b>(<i></i>) - -<p> - Private slot to react upon a change of selection in the slots list. -</p> <a NAME="EditWhiteListDialog.on_variablesList_itemSelectionChanged" ID="EditWhiteListDialog.on_variablesList_itemSelectionChanged"></a> <h4>EditWhiteListDialog.on_variablesList_itemSelectionChanged</h4> <b>on_variablesList_itemSelectionChanged</b>(<i></i>)
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/Documentation/source/Plugin_Checker_Vulture.VultureChecker.VultureCheckerDialog.html --- a/VultureChecker/Documentation/source/Plugin_Checker_Vulture.VultureChecker.VultureCheckerDialog.html Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/Documentation/source/Plugin_Checker_Vulture.VultureChecker.VultureCheckerDialog.html Sun Apr 25 16:13:53 2021 +0200 @@ -105,6 +105,38 @@ <td>Private slot to edit the whitelist.</td> </tr> <tr> +<td><a href="#VultureCheckerDialog.__filterUnusedAttributes">__filterUnusedAttributes</a></td> +<td>Private method to get the list of unused attributes.</td> +</tr> +<tr> +<td><a href="#VultureCheckerDialog.__filterUnusedClasses">__filterUnusedClasses</a></td> +<td>Private method to get the list of unused classes.</td> +</tr> +<tr> +<td><a href="#VultureCheckerDialog.__filterUnusedFunctions">__filterUnusedFunctions</a></td> +<td>Private method to get the list of unused functions.</td> +</tr> +<tr> +<td><a href="#VultureCheckerDialog.__filterUnusedImports">__filterUnusedImports</a></td> +<td>Private method to get a list of unused imports.</td> +</tr> +<tr> +<td><a href="#VultureCheckerDialog.__filterUnusedItems">__filterUnusedItems</a></td> +<td>Private method to get a list of unused items.</td> +</tr> +<tr> +<td><a href="#VultureCheckerDialog.__filterUnusedMethods">__filterUnusedMethods</a></td> +<td>Private method to get the list of unused methods.</td> +</tr> +<tr> +<td><a href="#VultureCheckerDialog.__filterUnusedProperties">__filterUnusedProperties</a></td> +<td>Private method to get the list of unused properties.</td> +</tr> +<tr> +<td><a href="#VultureCheckerDialog.__filterUnusedVariables">__filterUnusedVariables</a></td> +<td>Private method to get the list of unused variables.</td> +</tr> +<tr> <td><a href="#VultureCheckerDialog.__filteredList">__filteredList</a></td> <td>Private method to filter a list against the whitelist patterns returning items not matching the whitelist.</td> </tr> @@ -117,10 +149,6 @@ <td>Private method to get a list of selected non file items.</td> </tr> <tr> -<td><a href="#VultureCheckerDialog.__getUnusedItems">__getUnusedItems</a></td> -<td>Private method to get a list of unused items.</td> -</tr> -<tr> <td><a href="#VultureCheckerDialog.__prepareResultLists">__prepareResultLists</a></td> <td>Private method to prepare the result lists.</td> </tr> @@ -153,34 +181,6 @@ <td>Private method to store the new whitelists, if they have changed.</td> </tr> <tr> -<td><a href="#VultureCheckerDialog.__unusedAttributes">__unusedAttributes</a></td> -<td>Private method to get the list of unused attributes.</td> -</tr> -<tr> -<td><a href="#VultureCheckerDialog.__unusedClasses">__unusedClasses</a></td> -<td>Private method to get the list of unused classes.</td> -</tr> -<tr> -<td><a href="#VultureCheckerDialog.__unusedFunctions">__unusedFunctions</a></td> -<td>Private method to get the list of unused functions.</td> -</tr> -<tr> -<td><a href="#VultureCheckerDialog.__unusedImports">__unusedImports</a></td> -<td>Private method to get a list of unused imports.</td> -</tr> -<tr> -<td><a href="#VultureCheckerDialog.__unusedProperties">__unusedProperties</a></td> -<td>Private method to get the list of unused properties.</td> -</tr> -<tr> -<td><a href="#VultureCheckerDialog.__unusedSlots">__unusedSlots</a></td> -<td>Private method to get the list of unused PyQt/PySide slots.</td> -</tr> -<tr> -<td><a href="#VultureCheckerDialog.__unusedVariables">__unusedVariables</a></td> -<td>Private method to get the list of unused variables.</td> -</tr> -<tr> <td><a href="#VultureCheckerDialog.__whiteList">__whiteList</a></td> <td>Private slot to add entries to the whitelist.</td> </tr> @@ -281,7 +281,7 @@ </dd> </dl> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> reference to the created item </dd> @@ -332,7 +332,7 @@ </dd> </dl> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> vulture item </dd> @@ -350,6 +350,169 @@ <p> Private slot to edit the whitelist. </p> +<a NAME="VultureCheckerDialog.__filterUnusedAttributes" ID="VultureCheckerDialog.__filterUnusedAttributes"></a> +<h4>VultureCheckerDialog.__filterUnusedAttributes</h4> +<b>__filterUnusedAttributes</b>(<i></i>) + +<p> + Private method to get the list of unused attributes. +</p> +<dl> +<dt>Return:</dt> +<dd> +list of unused attributes +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +list of VultureItem +</dd> +</dl> +<a NAME="VultureCheckerDialog.__filterUnusedClasses" ID="VultureCheckerDialog.__filterUnusedClasses"></a> +<h4>VultureCheckerDialog.__filterUnusedClasses</h4> +<b>__filterUnusedClasses</b>(<i></i>) + +<p> + Private method to get the list of unused classes. +</p> +<dl> +<dt>Return:</dt> +<dd> +list of unused classes +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +list of VultureItem +</dd> +</dl> +<a NAME="VultureCheckerDialog.__filterUnusedFunctions" ID="VultureCheckerDialog.__filterUnusedFunctions"></a> +<h4>VultureCheckerDialog.__filterUnusedFunctions</h4> +<b>__filterUnusedFunctions</b>(<i></i>) + +<p> + Private method to get the list of unused functions. +</p> +<dl> +<dt>Return:</dt> +<dd> +list of unused functions +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +list of VultureItem +</dd> +</dl> +<a NAME="VultureCheckerDialog.__filterUnusedImports" ID="VultureCheckerDialog.__filterUnusedImports"></a> +<h4>VultureCheckerDialog.__filterUnusedImports</h4> +<b>__filterUnusedImports</b>(<i></i>) + +<p> + Private method to get a list of unused imports. +</p> +<dl> +<dt>Return:</dt> +<dd> +list of unused imports +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +list of VultureItem +</dd> +</dl> +<a NAME="VultureCheckerDialog.__filterUnusedItems" ID="VultureCheckerDialog.__filterUnusedItems"></a> +<h4>VultureCheckerDialog.__filterUnusedItems</h4> +<b>__filterUnusedItems</b>(<i>unused, whitelistName</i>) + +<p> + Private method to get a list of unused items. +</p> +<dl> + +<dt><i>unused</i> (list of VultureItem)</dt> +<dd> +list of unused items +</dd> +<dt><i>whitelistName</i> (str)</dt> +<dd> +name of the whitelist to use as a filter +</dd> +</dl> +<dl> +<dt>Return:</dt> +<dd> +list of unused items +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +list of VultureItem +</dd> +</dl> +<a NAME="VultureCheckerDialog.__filterUnusedMethods" ID="VultureCheckerDialog.__filterUnusedMethods"></a> +<h4>VultureCheckerDialog.__filterUnusedMethods</h4> +<b>__filterUnusedMethods</b>(<i></i>) + +<p> + Private method to get the list of unused methods. +</p> +<dl> +<dt>Return:</dt> +<dd> +list of unused methods +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +list of VultureItem +</dd> +</dl> +<a NAME="VultureCheckerDialog.__filterUnusedProperties" ID="VultureCheckerDialog.__filterUnusedProperties"></a> +<h4>VultureCheckerDialog.__filterUnusedProperties</h4> +<b>__filterUnusedProperties</b>(<i></i>) + +<p> + Private method to get the list of unused properties. +</p> +<dl> +<dt>Return:</dt> +<dd> +list of unused properties +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +list of VultureItem +</dd> +</dl> +<a NAME="VultureCheckerDialog.__filterUnusedVariables" ID="VultureCheckerDialog.__filterUnusedVariables"></a> +<h4>VultureCheckerDialog.__filterUnusedVariables</h4> +<b>__filterUnusedVariables</b>(<i></i>) + +<p> + Private method to get the list of unused variables. +</p> +<dl> +<dt>Return:</dt> +<dd> +list of unused variables +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +list of VultureItem +</dd> +</dl> <a NAME="VultureCheckerDialog.__filteredList" ID="VultureCheckerDialog.__filteredList"></a> <h4>VultureCheckerDialog.__filteredList</h4> <b>__filteredList</b>(<i>itemList</i>) @@ -366,7 +529,7 @@ </dd> </dl> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> list of filtered items </dd> @@ -393,7 +556,7 @@ Private method to get a list of selected non file items. </p> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> list of selected non file items </dd> @@ -404,36 +567,6 @@ list of QTreeWidgetItem </dd> </dl> -<a NAME="VultureCheckerDialog.__getUnusedItems" ID="VultureCheckerDialog.__getUnusedItems"></a> -<h4>VultureCheckerDialog.__getUnusedItems</h4> -<b>__getUnusedItems</b>(<i>defined, used</i>) - -<p> - Private method to get a list of unused items. -</p> -<dl> - -<dt><i>defined</i> (list of VultureItem)</dt> -<dd> -list of defined items -</dd> -<dt><i>used</i> (list of str)</dt> -<dd> -list of used names -</dd> -</dl> -<dl> -<dt>Returns:</dt> -<dd> -list of unused items -</dd> -</dl> -<dl> -<dt>Return Type:</dt> -<dd> -list of VultureItem -</dd> -</dl> <a NAME="VultureCheckerDialog.__prepareResultLists" ID="VultureCheckerDialog.__prepareResultLists"></a> <h4>VultureCheckerDialog.__prepareResultLists</h4> <b>__prepareResultLists</b>(<i></i>) @@ -533,139 +666,6 @@ dictionary of lists of whitelisted names </dd> </dl> -<a NAME="VultureCheckerDialog.__unusedAttributes" ID="VultureCheckerDialog.__unusedAttributes"></a> -<h4>VultureCheckerDialog.__unusedAttributes</h4> -<b>__unusedAttributes</b>(<i></i>) - -<p> - Private method to get the list of unused attributes. -</p> -<dl> -<dt>Returns:</dt> -<dd> -list of unused attributes -</dd> -</dl> -<dl> -<dt>Return Type:</dt> -<dd> -list of VultureItem -</dd> -</dl> -<a NAME="VultureCheckerDialog.__unusedClasses" ID="VultureCheckerDialog.__unusedClasses"></a> -<h4>VultureCheckerDialog.__unusedClasses</h4> -<b>__unusedClasses</b>(<i></i>) - -<p> - Private method to get the list of unused classes. -</p> -<dl> -<dt>Returns:</dt> -<dd> -list of unused classes -</dd> -</dl> -<dl> -<dt>Return Type:</dt> -<dd> -list of VultureItem -</dd> -</dl> -<a NAME="VultureCheckerDialog.__unusedFunctions" ID="VultureCheckerDialog.__unusedFunctions"></a> -<h4>VultureCheckerDialog.__unusedFunctions</h4> -<b>__unusedFunctions</b>(<i></i>) - -<p> - Private method to get the list of unused functions. -</p> -<dl> -<dt>Returns:</dt> -<dd> -list of unused functions -</dd> -</dl> -<dl> -<dt>Return Type:</dt> -<dd> -list of VultureItem -</dd> -</dl> -<a NAME="VultureCheckerDialog.__unusedImports" ID="VultureCheckerDialog.__unusedImports"></a> -<h4>VultureCheckerDialog.__unusedImports</h4> -<b>__unusedImports</b>(<i></i>) - -<p> - Private method to get a list of unused imports. -</p> -<dl> -<dt>Returns:</dt> -<dd> -list of unused imports -</dd> -</dl> -<dl> -<dt>Return Type:</dt> -<dd> -list of VultureItem -</dd> -</dl> -<a NAME="VultureCheckerDialog.__unusedProperties" ID="VultureCheckerDialog.__unusedProperties"></a> -<h4>VultureCheckerDialog.__unusedProperties</h4> -<b>__unusedProperties</b>(<i></i>) - -<p> - Private method to get the list of unused properties. -</p> -<dl> -<dt>Returns:</dt> -<dd> -list of unused properties -</dd> -</dl> -<dl> -<dt>Return Type:</dt> -<dd> -list of VultureItem -</dd> -</dl> -<a NAME="VultureCheckerDialog.__unusedSlots" ID="VultureCheckerDialog.__unusedSlots"></a> -<h4>VultureCheckerDialog.__unusedSlots</h4> -<b>__unusedSlots</b>(<i></i>) - -<p> - Private method to get the list of unused PyQt/PySide slots. -</p> -<dl> -<dt>Returns:</dt> -<dd> -list of unused PyQt/PySide slots -</dd> -</dl> -<dl> -<dt>Return Type:</dt> -<dd> -list of VultureItem -</dd> -</dl> -<a NAME="VultureCheckerDialog.__unusedVariables" ID="VultureCheckerDialog.__unusedVariables"></a> -<h4>VultureCheckerDialog.__unusedVariables</h4> -<b>__unusedVariables</b>(<i></i>) - -<p> - Private method to get the list of unused variables. -</p> -<dl> -<dt>Returns:</dt> -<dd> -list of unused variables -</dd> -</dl> -<dl> -<dt>Return Type:</dt> -<dd> -list of VultureItem -</dd> -</dl> <a NAME="VultureCheckerDialog.__whiteList" ID="VultureCheckerDialog.__whiteList"></a> <h4>VultureCheckerDialog.__whiteList</h4> <b>__whiteList</b>(<i></i>) @@ -782,7 +782,7 @@ Class to hold the name, type, confidence and location of defined code. </p> <h3>Derived from</h3> -object +None <h3>Class Attributes</h3> <table>
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/Documentation/source/Plugin_Checker_Vulture.VultureChecker.VultureCheckerService.html --- a/VultureChecker/Documentation/source/Plugin_Checker_Vulture.VultureChecker.VultureCheckerService.html Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/Documentation/source/Plugin_Checker_Vulture.VultureChecker.VultureCheckerService.html Sun Apr 25 16:13:53 2021 +0200 @@ -122,7 +122,7 @@ </dd> </dl> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> item dictionary </dd> @@ -141,7 +141,7 @@ Public method to get the scan results. </p> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> scan results </dd> @@ -174,7 +174,7 @@ </dd> </dl> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> tuple containing the result dictionary </dd> @@ -229,7 +229,7 @@ Initialize the batch service and return the entry point. </p> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> the entry point for the background client (function) </dd> @@ -245,7 +245,7 @@ Initialize the service and return the entry point. </p> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> the entry point for the background client (function) </dd> @@ -272,7 +272,7 @@ </dd> </dl> <dl> -<dt>Returns:</dt> +<dt>Return:</dt> <dd> tuple containing the result dictionary </dd>
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/EditWhiteListDialog.py --- a/VultureChecker/EditWhiteListDialog.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/EditWhiteListDialog.py Sun Apr 25 16:13:53 2021 +0200 @@ -26,13 +26,13 @@ @param parent reference to the parent widget @type QWidget """ - super(EditWhiteListDialog, self).__init__(parent) + super().__init__(parent) self.setupUi(self) self.__lists = [ self.classesList, self.functionsList, - self.slotsList, + self.methodsList, self.attributesList, self.variablesList, self.propertiesList, @@ -42,7 +42,7 @@ self.classesList.addItems(whitelists["class"]) self.functionsList.addItems(whitelists["function"]) - self.slotsList.addItems(whitelists["slot"]) + self.methodsList.addItems(whitelists["method"]) self.attributesList.addItems(whitelists["attribute"]) self.variablesList.addItems(whitelists["variable"]) self.propertiesList.addItems(whitelists["property"]) @@ -89,16 +89,16 @@ self.__setButtonEnabledStates() @pyqtSlot() - def on_classesList_itemSelectionChanged(self): + def on_methodsList_itemSelectionChanged(self): """ - Private slot to react upon a change of selection in the classes list. + Private slot to react upon a change of selection in the methods list. """ self.__setButtonEnabledStates() @pyqtSlot() - def on_slotsList_itemSelectionChanged(self): + def on_classesList_itemSelectionChanged(self): """ - Private slot to react upon a change of selection in the slots list. + Private slot to react upon a change of selection in the classes list. """ self.__setButtonEnabledStates() @@ -118,11 +118,7 @@ @return flag indicating a wildcard pattern @rtype bool """ - for char in "*?[": - if char in name: - return True - - return False + return any(char in name for char in "*?[") @pyqtSlot() def on_addButton_clicked(self): @@ -203,7 +199,7 @@ return { "class": self.__getWhiteList(self.classesList), "function": self.__getWhiteList(self.functionsList), - "slot": self.__getWhiteList(self.slotsList), + "method": self.__getWhiteList(self.methodsList), "attribute": self.__getWhiteList(self.attributesList), "variable": self.__getWhiteList(self.variablesList), "property": self.__getWhiteList(self.propertiesList),
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/EditWhiteListDialog.ui --- a/VultureChecker/EditWhiteListDialog.ui Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/EditWhiteListDialog.ui Sun Apr 25 16:13:53 2021 +0200 @@ -22,7 +22,7 @@ <item> <widget class="QTabWidget" name="listsWidget"> <property name="currentIndex"> - <number>0</number> + <number>7</number> </property> <widget class="QWidget" name="tab_1"> <attribute name="title"> @@ -64,13 +64,13 @@ </item> </layout> </widget> - <widget class="QWidget" name="tab_3"> + <widget class="QWidget" name="tab_9"> <attribute name="title"> - <string>PyQt/PySide Slots</string> + <string>Methods</string> </attribute> - <layout class="QVBoxLayout" name="verticalLayout_8"> + <layout class="QVBoxLayout" name="verticalLayout_11"> <item> - <widget class="QListWidget" name="slotsList"> + <widget class="QListWidget" name="methodsList"> <property name="alternatingRowColors"> <bool>true</bool> </property> @@ -274,7 +274,7 @@ <tabstop>listsWidget</tabstop> <tabstop>classesList</tabstop> <tabstop>functionsList</tabstop> - <tabstop>slotsList</tabstop> + <tabstop>methodsList</tabstop> <tabstop>attributesList</tabstop> <tabstop>variablesList</tabstop> <tabstop>propertiesList</tabstop>
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/VultureCheckerDialog.py --- a/VultureChecker/VultureCheckerDialog.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/VultureCheckerDialog.py Sun Apr 25 16:13:53 2021 +0200 @@ -9,6 +9,7 @@ import os import fnmatch +import contextlib from PyQt5.QtCore import pyqtSlot, qVersion, Qt, QTimer, QRegExp from PyQt5.QtWidgets import ( @@ -24,7 +25,7 @@ import Utilities -class VultureItem(object): +class VultureItem: """ Class to hold the name, type, confidence and location of defined code. """ @@ -70,7 +71,7 @@ @param parent reference to the parent widget @type QWidget """ - super(VultureCheckerDialog, self).__init__(parent) + super().__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.Window) @@ -112,7 +113,7 @@ self.__translatedTypes = { "property": self.tr("Property"), "function": self.tr("Function"), - "slot": self.tr("Slot"), + "method": self.tr("Methode"), "attribute": self.tr("Attribute"), "variable": self.tr("Variable"), "class": self.tr("Class"), @@ -169,7 +170,6 @@ self.__data["WhiteLists"] = { "property": [], "function": [], - "slot": [], "attribute": [], "variable": [], "class": [], @@ -179,13 +179,12 @@ "visit_*", ], } + if "method" not in self.__data["WhiteLists"]: + self.__data["WhiteLists"]["method"] = [] if "import" not in self.__data["WhiteLists"]: self.__data["WhiteLists"]["import"] = [] - if "SlotsAreUsed" not in self.__data: - self.__data["SlotsAreUsed"] = True self.excludeFilesEdit.setText(self.__data["ExcludeFiles"]) - self.slotsCheckBox.setChecked(self.__data["SlotsAreUsed"]) def start(self, fn): """ @@ -285,11 +284,9 @@ The results are reported to the __processResult slot. """ self.checkProgressLabel.setPath(self.tr("Preparing files...")) - progress = 0 argumentsList = [] - for filename in self.files: - progress += 1 + for progress, filename in enumerate(self.files, start=1): self.checkProgress.setValue(progress) QApplication.processEvents() @@ -438,12 +435,6 @@ fileList = [f for f in fileList if not fnmatch.fnmatch(f, fileFilter)] - self.__slotsAreUsed = self.slotsCheckBox.isChecked() - if self.__slotsAreUsed != self.__data["SlotsAreUsed"]: - self.__data["SlotsAreUsed"] = self.__slotsAreUsed - self.__project.setData( - "CHECKERSPARMS", "Vulture", self.__data) - self.start(fileList) def clear(self): @@ -476,16 +467,13 @@ """ Private method to prepare the result lists. """ - self.__definedAttrs = [] - self.__definedClasses = [] - self.__definedFuncs = [] - self.__definedImports = [] - self.__definedSlots = [] - self.__definedProps = [] - self.__definedVars = [] - - self.__usedAttrs = [] - self.__usedNames = [] + self.__unusedAttrs = [] + self.__unusedClasses = [] + self.__unusedFuncs = [] + self.__unusedMethods = [] + self.__unusedImports = [] + self.__unusedProps = [] + self.__unusedVars = [] def __storeResult(self, result): """ @@ -494,22 +482,20 @@ @param result result dictionary @type dict """ - self.__definedAttrs.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["DefinedAttributes"]])) - self.__definedClasses.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["DefinedClasses"]])) - self.__definedFuncs.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["DefinedFunctions"]])) - self.__definedImports.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["DefinedImports"]])) - self.__definedSlots.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["DefinedSlots"]])) - self.__definedProps.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["DefinedProperties"]])) - self.__definedVars.extend(self.__filteredList( - [self.__dict2Item(d) for d in result["DefinedVariables"]])) - self.__usedAttrs.extend(result["UsedAttributes"]) - self.__usedNames.extend(result["UsedNames"]) + 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): """ @@ -540,109 +526,84 @@ if not regExp.exactMatch(item.name)] return filteredList # __IGNORE_WARNING_M834__ - def __getUnusedItems(self, defined, used): + def __filterUnusedItems(self, unused, whitelistName): """ Private method to get a list of unused items. - @param defined list of defined items + @param unused list of unused items @type list of VultureItem - @param used list of used names - @type list of str + @param whitelistName name of the whitelist to use as a filter + @type str @return list of unused items @rtype list of VultureItem """ - return [item for item in set(defined) if item.name not in used] + return [ + item for item in set(unused) + if item.name not in self.__data["WhiteLists"][whitelistName] + ] - def __unusedFunctions(self): + def __filterUnusedFunctions(self): """ Private method to get the list of unused functions. @return list of unused functions @rtype list of VultureItem """ - return self.__getUnusedItems( - self.__definedFuncs, - self.__usedAttrs + self.__usedNames + - self.__data["WhiteLists"]["function"] - ) + return self.__filterUnusedItems(self.__unusedFuncs, "function") - def __unusedSlots(self): + def __filterUnusedMethods(self): """ - Private method to get the list of unused PyQt/PySide slots. + Private method to get the list of unused methods. - @return list of unused PyQt/PySide slots + @return list of unused methods @rtype list of VultureItem """ - return self.__getUnusedItems( - self.__definedSlots, - self.__usedAttrs + self.__usedNames + - self.__data["WhiteLists"]["slot"] - ) + return self.__filterUnusedItems(self.__unusedMethods, "method") - def __unusedClasses(self): + def __filterUnusedClasses(self): """ Private method to get the list of unused classes. @return list of unused classes @rtype list of VultureItem """ - return self.__getUnusedItems( - self.__definedClasses, - self.__usedAttrs + self.__usedNames + - self.__data["WhiteLists"]["class"] - ) + return self.__filterUnusedItems(self.__unusedClasses, "class") - def __unusedImports(self): + def __filterUnusedImports(self): """ Private method to get a list of unused imports. @return list of unused imports @rtype list of VultureItem """ - return self.__getUnusedItems( - self.__definedClasses, - self.__usedAttrs + self.__usedNames + - self.__data["WhiteLists"]["import"] - ) + return self.__filterUnusedItems(self.__unusedImports, "import") - def __unusedProperties(self): + def __filterUnusedProperties(self): """ Private method to get the list of unused properties. @return list of unused properties @rtype list of VultureItem """ - return self.__getUnusedItems( - self.__definedProps, - self.__usedAttrs + - self.__data["WhiteLists"]["property"] - ) + return self.__filterUnusedItems(self.__unusedProps, "property") - def __unusedVariables(self): + def __filterUnusedVariables(self): """ Private method to get the list of unused variables. @return list of unused variables @rtype list of VultureItem """ - return self.__getUnusedItems( - self.__definedVars, - self.__usedAttrs + self.__usedNames + - self.__data["WhiteLists"]["variable"] - ) + return self.__filterUnusedItems(self.__unusedVars, "variable") - def __unusedAttributes(self): + def __filterUnusedAttributes(self): """ Private method to get the list of unused attributes. @return list of unused attributes @rtype list of VultureItem """ - return self.__getUnusedItems( - self.__definedAttrs, - self.__usedAttrs + - self.__data["WhiteLists"]["attribute"] - ) + return self.__filterUnusedItems(self.__unusedAttrs, "attribute") def __createResultItems(self): """ @@ -650,14 +611,13 @@ """ lastFileItem = None lastFileName = "" - items = (self.__unusedFunctions() + - self.__unusedClasses() + - self.__unusedImports() + - self.__unusedProperties() + - self.__unusedVariables() + - self.__unusedAttributes()) - if not self.__slotsAreUsed: - items += self.__unusedSlots() + 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) @@ -761,11 +721,8 @@ for key in self.__data["WhiteLists"]: whitelists[key] = self.__data["WhiteLists"][key][:] for itm in self.__getSelectedNonFileItems(): - try: + with contextlib.suppress(KeyError): whitelists[itm.data(0, self.TypeRole)].append(itm.text(1)) - except KeyError: - # ignore non-existing types - pass # remove the item from the result list pitm = itm.parent() pitm.removeChild(itm) @@ -786,13 +743,10 @@ changed = False for key in whitelists: whitelist = list(set(whitelists[key])) - try: + with contextlib.suppress(KeyError): if sorted(whitelist) != sorted(self.__data["WhiteLists"][key]): self.__data["WhiteLists"][key] = whitelist[:] changed = True - except KeyError: - # ignore non-existing types - pass if changed: self.__project.setData("CHECKERSPARMS", "Vulture", self.__data)
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/VultureCheckerDialog.ui --- a/VultureChecker/VultureCheckerDialog.ui Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/VultureCheckerDialog.ui Sun Apr 25 16:13:53 2021 +0200 @@ -29,7 +29,7 @@ <property name="frameShadow"> <enum>QFrame::Raised</enum> </property> - <layout class="QGridLayout" name="gridLayout"> + <layout class="QHBoxLayout" name="horizontalLayout"> <property name="leftMargin"> <number>0</number> </property> @@ -42,21 +42,21 @@ <property name="bottomMargin"> <number>0</number> </property> - <item row="0" column="0"> + <item> <widget class="QLabel" name="label_2"> <property name="text"> <string>Exclude Files:</string> </property> </widget> </item> - <item row="0" column="1"> + <item> <widget class="E5ClearableLineEdit" name="excludeFilesEdit"> <property name="toolTip"> <string>Enter filename patterns of files to be excluded separated by a comma</string> </property> </widget> </item> - <item row="0" column="2" rowspan="2"> + <item> <widget class="Line" name="line"> <property name="lineWidth"> <number>2</number> @@ -66,7 +66,7 @@ </property> </widget> </item> - <item row="0" column="3" rowspan="2"> + <item> <widget class="QPushButton" name="startButton"> <property name="toolTip"> <string>Press to start the check</string> @@ -76,16 +76,6 @@ </property> </widget> </item> - <item row="1" column="0" colspan="2"> - <widget class="QCheckBox" name="slotsCheckBox"> - <property name="toolTip"> - <string>Select to treat all PyQt slots as used</string> - </property> - <property name="text"> - <string>Treat PyQt slots as used</string> - </property> - </widget> - </item> </layout> </widget> </item> @@ -180,7 +170,6 @@ <tabstops> <tabstop>startButton</tabstop> <tabstop>excludeFilesEdit</tabstop> - <tabstop>slotsCheckBox</tabstop> <tabstop>resultList</tabstop> </tabstops> <resources/>
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/VultureCheckerService.py --- a/VultureChecker/VultureCheckerService.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/VultureCheckerService.py Sun Apr 25 16:13:53 2021 +0200 @@ -166,7 +166,7 @@ return { "name": item.name, "type": item.typ, - "file": item.filename, + "file": str(item.filename), "first_line": item.first_lineno, "last_line": item.last_lineno, "confidence": item.confidence, @@ -180,20 +180,18 @@ @rtype dict """ return { - "DefinedAttributes": - [self.__item2Dict(i) for i in self.defined_attrs], - "DefinedClasses": - [self.__item2Dict(i) for i in self.defined_classes], - "DefinedFunctions": - [self.__item2Dict(i) for i in self.defined_funcs], - "DefinedImports": - [self.__item2Dict(i) for i in self.defined_imports], - "DefinedSlots": - [self.__item2Dict(i) for i in self.defined_slots], - "DefinedProperties": - [self.__item2Dict(i) for i in self.defined_props], - "DefinedVariables": - [self.__item2Dict(i) for i in self.defined_vars], - "UsedAttributes": list(self.used_attrs), - "UsedNames": list(self.used_names), + "UnusedAttributes": + [self.__item2Dict(i) for i in self.unused_attrs], + "UnusedClasses": + [self.__item2Dict(i) for i in self.unused_classes], + "UnusedFunctions": + [self.__item2Dict(i) for i in self.unused_funcs], + "UnusedMethods": + [self.__item2Dict(i) for i in self.unused_methods], + "UnusedImports": + [self.__item2Dict(i) for i in self.unused_imports], + "UnusedProperties": + [self.__item2Dict(i) for i in self.unused_props], + "UnusedVariables": + [self.__item2Dict(i) for i in self.unused_vars], }
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VultureChecker/vulture/LICENSE.txt Sun Apr 25 16:13:53 2021 +0200 @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2012-2020 Jendrik Seipp (jendrikseipp@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/__init__.py --- a/VultureChecker/vulture/__init__.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/vulture/__init__.py Sun Apr 25 16:13:53 2021 +0200 @@ -1,4 +1,5 @@ -from vulture.core import __version__, Vulture +from vulture.core import Vulture +from vulture.version import __version__ assert __version__ assert Vulture
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/config.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VultureChecker/vulture/config.py Sun Apr 25 16:13:53 2021 +0200 @@ -0,0 +1,208 @@ +""" +This module handles retrieval of configuration values from either the +command-line arguments or the pyproject.toml file. +""" +import argparse +import pathlib +import sys + +import toml + +from .version import __version__ + +#: Possible configuration options and their respective defaults +DEFAULTS = { + "min_confidence": 0, + "paths": [], + "exclude": [], + "ignore_decorators": [], + "ignore_names": [], + "make_whitelist": False, + "sort_by_size": False, + "verbose": False, +} + + +def _check_input_config(data): + """ + Checks the types of the values in *data* against the expected types of + config-values. If a value is of the wrong type it will raise a SystemExit. + """ + for key, value in data.items(): + if key not in DEFAULTS: + sys.exit(f"Unknown configuration key: {key}") + # The linter suggests to use "isinstance" here but this fails to + # detect the difference between `int` and `bool`. + if type(value) is not type(DEFAULTS[key]): # noqa: E721 + expected_type = type(DEFAULTS[key]).__name__ + sys.exit(f"Data type for {key} must be {expected_type!r}") + + +def _check_output_config(config): + """ + Run sanity checks on the generated config after all parsing and + preprocessing is done. + + Exit the application if an error is encountered. + """ + if not config["paths"]: + sys.exit("Please pass at least one file or directory") + + +def _parse_toml(infile): + """ + Parse a TOML file for config values. + + It will search for a section named ``[tool.vulture]`` which contains the + same keys as the CLI arguments seen with ``--help``. All leading dashes are + removed and other dashes are replaced by underscores (so ``--sort-by-size`` + becomes ``sort_by_size``). + + Arguments containing multiple values are standard TOML lists. + + Example:: + + [tool.vulture] + exclude = ["file*.py", "dir/"] + ignore_decorators = ["deco1", "deco2"] + ignore_names = ["name1", "name2"] + make_whitelist = true + min_confidence = 10 + sort_by_size = true + verbose = true + paths = ["path1", "path2"] + """ + data = toml.load(infile) + settings = data.get("tool", {}).get("vulture", {}) + _check_input_config(settings) + return settings + + +def _parse_args(args=None): + """ + Parse CLI arguments. + + :param args: A list of strings representing the CLI arguments. If left to + the default, this will default to ``sys.argv``. + """ + + # Sentinel value to distinguish between "False" and "no default given". + missing = object() + + def csv(exclude): + return exclude.split(",") + + usage = "%(prog)s [options] [PATH ...]" + version = f"vulture {__version__}" + glob_help = "Patterns may contain glob wildcards (*, ?, [abc], [!abc])." + parser = argparse.ArgumentParser(prog="vulture", usage=usage) + parser.add_argument( + "paths", + nargs="*", + metavar="PATH", + default=missing, + help="Paths may be Python files or directories. For each directory" + " Vulture analyzes all contained *.py files.", + ) + parser.add_argument( + "--exclude", + metavar="PATTERNS", + type=csv, + default=missing, + help=f"Comma-separated list of paths to ignore (e.g.," + f' "*settings.py,docs/*.py"). {glob_help} A PATTERN without glob' + f" wildcards is treated as *PATTERN*.", + ) + parser.add_argument( + "--ignore-decorators", + metavar="PATTERNS", + type=csv, + default=missing, + help=f"Comma-separated list of decorators. Functions and classes using" + f' these decorators are ignored (e.g., "@app.route,@require_*").' + f" {glob_help}", + ) + parser.add_argument( + "--ignore-names", + metavar="PATTERNS", + type=csv, + default=missing, + help=f'Comma-separated list of names to ignore (e.g., "visit_*,do_*").' + f" {glob_help}", + ) + parser.add_argument( + "--make-whitelist", + action="store_true", + default=missing, + help="Report unused code in a format that can be added to a" + " whitelist module.", + ) + parser.add_argument( + "--min-confidence", + type=int, + default=missing, + help="Minimum confidence (between 0 and 100) for code to be" + " reported as unused.", + ) + parser.add_argument( + "--sort-by-size", + action="store_true", + default=missing, + help="Sort unused functions and classes by their lines of code.", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", default=missing + ) + parser.add_argument("--version", action="version", version=version) + namespace = parser.parse_args(args) + cli_args = { + key: value + for key, value in vars(namespace).items() + if value is not missing + } + _check_input_config(cli_args) + return cli_args + + +def make_config(argv=None, tomlfile=None): + """ + Returns a config object for vulture, merging both ``pyproject.toml`` and + CLI arguments (CLI arguments have precedence). + + :param argv: The CLI arguments to be parsed. This value is transparently + passed through to :py:meth:`argparse.ArgumentParser.parse_args`. + :param tomlfile: An IO instance containing TOML data. By default this will + auto-detect an existing ``pyproject.toml`` file and exists solely for + unit-testing. + """ + # If we loaded data from a TOML file, we want to print this out on stdout + # in verbose mode so we need to keep the value around. + detected_toml_path = "" + + if tomlfile: + config = _parse_toml(tomlfile) + detected_toml_path = str(tomlfile) + else: + toml_path = pathlib.Path("pyproject.toml").resolve() + if toml_path.is_file(): + with open(toml_path) as fconfig: + config = _parse_toml(fconfig) + detected_toml_path = str(toml_path) + else: + config = {} + + cli_config = _parse_args(argv) + + # Overwrite TOML options with CLI options, if given. + config.update(cli_config) + + # Set defaults for missing options. + for key, value in DEFAULTS.items(): + config.setdefault(key, value) + + if detected_toml_path and config["verbose"]: + print(f"Reading configuration from {detected_toml_path}") + + _check_output_config(config) + + return config
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/core.py --- a/VultureChecker/vulture/core.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/vulture/core.py Sun Apr 25 16:13:53 2021 +0200 @@ -1,91 +1,79 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- -# -# vulture - Find dead code. -# -# Copyright (c) 2012-2018 Jendrik Seipp (jendrikseipp@gmail.com) -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be included -# in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -from __future__ import print_function - -import argparse import ast from fnmatch import fnmatch, fnmatchcase -import os.path +from pathlib import Path import pkgutil import re import string import sys from vulture import lines +from vulture import noqa from vulture import utils +from vulture.config import make_config -__version__ = '1.0-eric6' DEFAULT_CONFIDENCE = 60 -# The ast module in Python 2 trips over "coding" cookies, so strip them. -ENCODING_REGEX = re.compile( - r"^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+).*?$", flags=re.M) +IGNORED_VARIABLE_NAMES = {"object", "self"} -IGNORED_VARIABLE_NAMES = set(['object', 'self']) -# True and False are NameConstants since Python 3.4. -if sys.version_info < (3, 4): - IGNORED_VARIABLE_NAMES |= set(['True', 'False']) +ERROR_CODES = { + "attribute": "V101", + "class": "V102", + "function": "V103", + "import": "V104", + "method": "V105", + "property": "V106", + "variable": "V107", + "unreachable_code": "V201", +} def _get_unused_items(defined_items, used_names): - unused_items = [item for item in set(defined_items) - if item.name not in used_names] + unused_items = [ + item for item in set(defined_items) if item.name not in used_names + ] unused_items.sort(key=lambda item: item.name.lower()) return unused_items def _is_special_name(name): - return name.startswith('__') and name.endswith('__') + return name.startswith("__") and name.endswith("__") -def _match(name, patterns): - return any(fnmatchcase(name, pattern) for pattern in patterns) +def _match(name, patterns, case=True): + func = fnmatchcase if case else fnmatch + return any(func(name, pattern) for pattern in patterns) def _is_test_file(filename): - return any( - fnmatch(os.path.basename(filename), pattern) - for pattern in ['test*.py', '*_test.py', '*-test.py']) + return _match( + filename.resolve(), + ["*/test/*", "*/tests/*", "*/test*.py", "*[-_]test.py"], + case=False, + ) def _ignore_class(filename, class_name): - return _is_test_file(filename) and 'Test' in class_name + return _is_test_file(filename) and "Test" in class_name -def _ignore_import(_filename, import_name): - # Ignore star-imported names, since we can't detect whether they are used. - return import_name == '*' +def _ignore_import(filename, import_name): + """ + Ignore star-imported names since we can't detect whether they are used. + Ignore imports from __init__.py files since they're commonly used to + collect objects from a package. + """ + return filename.name == "__init__.py" or import_name == "*" def _ignore_function(filename, function_name): - return ( - _is_special_name(function_name) or - (function_name.startswith('test_') and _is_test_file(filename))) + return function_name.startswith("test_") and _is_test_file(filename) + + +def _ignore_method(filename, method_name): + return _is_special_name(method_name) or ( + method_name.startswith("test_") and _is_test_file(filename) + ) def _ignore_variable(filename, varname): @@ -94,25 +82,43 @@ __x__ (special variable or method), but not __x. """ return ( - varname in IGNORED_VARIABLE_NAMES or - (varname.startswith('_') and not varname.startswith('__')) or - _is_special_name(varname)) + varname in IGNORED_VARIABLE_NAMES + or (varname.startswith("_") and not varname.startswith("__")) + or _is_special_name(varname) + ) -class Item(object): +class Item: """ Hold the name, type and location of defined code. """ - def __init__(self, name, typ, filename, first_lineno, last_lineno, - message='', - confidence=DEFAULT_CONFIDENCE): + __slots__ = ( + "name", + "typ", + "filename", + "first_lineno", + "last_lineno", + "message", + "confidence", + ) + + def __init__( + self, + name, + typ, + filename, + first_lineno, + last_lineno, + message="", + confidence=DEFAULT_CONFIDENCE, + ): self.name = name self.typ = typ self.filename = filename self.first_lineno = first_lineno self.last_lineno = last_lineno - self.message = message or "unused {typ} '{name}'".format(**locals()) + self.message = message or f"unused {typ} '{name}'" self.confidence = confidence @property @@ -122,23 +128,29 @@ def get_report(self, add_size=False): if add_size: - line_format = 'line' if self.size == 1 else 'lines' - size_report = ', {0:d} {1}'.format(self.size, line_format) + line_format = "line" if self.size == 1 else "lines" + size_report = f", {self.size:d} {line_format}" else: - size_report = '' - return "{0}:{1:d}: {2} ({3}% confidence{4})".format( - utils.format_path(self.filename), self.first_lineno, - self.message, self.confidence, size_report) + size_report = "" + return "{}:{:d}: {} ({}% confidence{})".format( + utils.format_path(self.filename), + self.first_lineno, + self.message, + self.confidence, + size_report, + ) def get_whitelist_string(self): filename = utils.format_path(self.filename) - if self.typ == 'unreachable_code': - return ('# {} ({}:{})'.format( - self.message, filename, self.first_lineno)) + if self.typ == "unreachable_code": + return f"# {self.message} ({filename}:{self.first_lineno})" else: - prefix = '_.' if self.typ in ['attribute', 'property'] else '' + prefix = "" + if self.typ in ["attribute", "method", "property"]: + prefix = "_." return "{}{} # unused {} ({}:{:d})".format( - prefix, self.name, self.typ, filename, self.first_lineno) + prefix, self.name, self.typ, filename, self.first_lineno + ) def _tuple(self): return (self.filename, self.first_lineno, self.name) @@ -156,94 +168,110 @@ class Vulture(ast.NodeVisitor): """Find dead code.""" - def __init__(self, verbose=False, ignore_names=None, - ignore_decorators=None): + def __init__( + self, verbose=False, ignore_names=None, ignore_decorators=None + ): self.verbose = verbose def get_list(typ): return utils.LoggingList(typ, self.verbose) - def get_set(typ): - return utils.LoggingSet(typ, self.verbose) + self.defined_attrs = get_list("attribute") + self.defined_classes = get_list("class") + self.defined_funcs = get_list("function") + self.defined_imports = get_list("import") + self.defined_methods = get_list("method") + self.defined_props = get_list("property") + self.defined_vars = get_list("variable") + self.unreachable_code = get_list("unreachable_code") - self.defined_attrs = get_list('attribute') - self.defined_classes = get_list('class') - self.defined_funcs = get_list('function') - self.defined_imports = get_list('import') - self.defined_props = get_list('property') - self.defined_slots = get_list('slot') - # @pyqtSlot and @Slot support - eric6 - self.defined_vars = get_list('variable') - self.unreachable_code = get_list('unreachable_code') - - self.used_attrs = get_set('attribute') - self.used_names = get_set('name') + self.used_names = utils.LoggingSet("name", self.verbose) self.ignore_names = ignore_names or [] self.ignore_decorators = ignore_decorators or [] - self.filename = '' + self.filename = Path() self.code = [] self.found_dead_code_or_error = False - def scan(self, code, filename=''): - code = ENCODING_REGEX.sub("", code, count=1) + def scan(self, code, filename=""): + filename = Path(filename) self.code = code.splitlines() + self.noqa_lines = noqa.parse_noqa(self.code) self.filename = filename + + def handle_syntax_error(e): + text = f' at "{e.text.strip()}"' if e.text else "" + print( + f"{utils.format_path(filename)}:{e.lineno}: {e.msg}{text}", + file=sys.stderr, + ) + self.found_dead_code_or_error = True + try: - node = ast.parse(code, filename=self.filename) + node = ( + ast.parse( + code, filename=str(self.filename), type_comments=True + ) + if sys.version_info >= (3, 8) # type_comments requires 3.8+ + else ast.parse(code, filename=str(self.filename)) + ) except SyntaxError as err: - text = ' at "{0}"'.format(err.text.strip()) if err.text else '' - print('{0}:{1:d}: {2}{3}'.format( - utils.format_path(filename), err.lineno, err.msg, text), - file=sys.stderr) - self.found_dead_code_or_error = True - except (TypeError, ValueError) as err: - # Python < 3.5 raises TypeError and Python >= 3.5 raises - # ValueError if source contains null bytes. - print('{0}: invalid source code "{1}"'.format( - utils.format_path(filename), err), file=sys.stderr) + handle_syntax_error(err) + except ValueError as err: + # ValueError is raised if source contains null bytes. + print( + f'{utils.format_path(filename)}: invalid source code "{err}"', + file=sys.stderr, + ) self.found_dead_code_or_error = True else: - self.visit(node) + # When parsing type comments, visiting can throw SyntaxError. + try: + self.visit(node) + except SyntaxError as err: + handle_syntax_error(err) def scavenge(self, paths, exclude=None): def prepare_pattern(pattern): - if not any(char in pattern for char in ['*', '?', '[']): - pattern = '*{pattern}*'.format(**locals()) + if not any(char in pattern for char in "*?["): + pattern = f"*{pattern}*" return pattern exclude = [prepare_pattern(pattern) for pattern in (exclude or [])] - def exclude_file(name): - return any(fnmatch(name, pattern) for pattern in exclude) + def exclude_path(path): + return _match(path, exclude, case=False) + + paths = [Path(path) for path in paths] for module in utils.get_modules(paths): - if exclude_file(module): - self._log('Excluded:', module) + if exclude_path(module): + self._log("Excluded:", module) continue - self._log('Scanning:', module) + self._log("Scanning:", module) try: module_string = utils.read_file(module) - except utils.VultureInputException as err: + except utils.VultureInputException as err: # noqa: F841 print( - 'Error: Could not read file {module} - {err}\n' - 'Try to change the encoding to UTF-8.'.format(**locals()), - file=sys.stderr) + f"Error: Could not read file {module} - {err}\n" + f"Try to change the encoding to UTF-8.", + file=sys.stderr, + ) self.found_dead_code_or_error = True else: self.scan(module_string, filename=module) - unique_imports = set(item.name for item in self.defined_imports) + unique_imports = {item.name for item in self.defined_imports} for import_name in unique_imports: - path = os.path.join('whitelists', import_name) + '_whitelist.py' - if exclude_file(path): - self._log('Excluded whitelist:', path) + path = Path("whitelists") / (import_name + "_whitelist.py") + if exclude_path(path): + self._log("Excluded whitelist:", path) else: try: - module_data = pkgutil.get_data('vulture', path) - self._log('Included whitelist:', path) + module_data = pkgutil.get_data("vulture", str(path)) + self._log("Included whitelist:", path) except OSError: # Most imported modules don't have a whitelist. continue @@ -255,68 +283,77 @@ Return ordered list of unused Item objects. """ if not 0 <= min_confidence <= 100: - raise ValueError('min_confidence must be between 0 and 100.') + raise ValueError("min_confidence must be between 0 and 100.") def by_name(item): - return (item.filename.lower(), item.first_lineno) + return (str(item.filename).lower(), item.first_lineno) def by_size(item): return (item.size,) + by_name(item) - unused_code = (self.unused_attrs + self.unused_classes + - self.unused_funcs + self.unused_imports + - self.unused_props + self.unused_vars + - self.unreachable_code) + unused_code = ( + self.unused_attrs + + self.unused_classes + + self.unused_funcs + + self.unused_imports + + self.unused_methods + + self.unused_props + + self.unused_vars + + self.unreachable_code + ) - confidently_unused = [obj for obj in unused_code - if obj.confidence >= min_confidence] + confidently_unused = [ + obj for obj in unused_code if obj.confidence >= min_confidence + ] - return sorted(confidently_unused, - key=by_size if sort_by_size else by_name) + return sorted( + confidently_unused, key=by_size if sort_by_size else by_name + ) - def report(self, min_confidence=0, sort_by_size=False, - make_whitelist=False): + def report( + self, min_confidence=0, sort_by_size=False, make_whitelist=False + ): """ Print ordered list of Item objects to stdout. """ for item in self.get_unused_code( - min_confidence=min_confidence, sort_by_size=sort_by_size): - print(item.get_whitelist_string() if make_whitelist - else item.get_report(add_size=sort_by_size)) + min_confidence=min_confidence, sort_by_size=sort_by_size + ): + print( + item.get_whitelist_string() + if make_whitelist + else item.get_report(add_size=sort_by_size) + ) self.found_dead_code_or_error = True return self.found_dead_code_or_error @property def unused_classes(self): - return _get_unused_items( - self.defined_classes, - self.used_attrs | self.used_names) + return _get_unused_items(self.defined_classes, self.used_names) @property def unused_funcs(self): - return _get_unused_items( - self.defined_funcs, - self.used_attrs | self.used_names) + return _get_unused_items(self.defined_funcs, self.used_names) @property def unused_imports(self): - return _get_unused_items( - self.defined_imports, - self.used_names | self.used_attrs) + return _get_unused_items(self.defined_imports, self.used_names) + + @property + def unused_methods(self): + return _get_unused_items(self.defined_methods, self.used_names) @property def unused_props(self): - return _get_unused_items(self.defined_props, self.used_attrs) + return _get_unused_items(self.defined_props, self.used_names) @property def unused_vars(self): - return _get_unused_items( - self.defined_vars, - self.used_attrs | self.used_names) + return _get_unused_items(self.defined_vars, self.used_names) @property def unused_attrs(self): - return _get_unused_items(self.defined_attrs, self.used_attrs) + return _get_unused_items(self.defined_attrs, self.used_names) def _log(self, *args): if self.verbose: @@ -331,54 +368,107 @@ for name_and_alias in node.names: # Store only top-level module name ("os.path" -> "os"). # We can't easily detect when "os.path" is used. - name = name_and_alias.name.partition('.')[0] + name = name_and_alias.name.partition(".")[0] alias = name_and_alias.asname self._define( - self.defined_imports, alias or name, node, - confidence=90, ignore=_ignore_import) + self.defined_imports, + alias or name, + node, + confidence=90, + ignore=_ignore_import, + ) if alias is not None: self.used_names.add(name_and_alias.name) def _handle_conditional_node(self, node, name): if utils.condition_is_always_false(node.test): self._define( - self.unreachable_code, name, node, - last_node=node.body[-1], - message="unsatisfiable '{name}' condition".format(**locals()), - confidence=100) - else: - else_body = getattr(node, 'orelse') - if utils.condition_is_always_true(node.test) and else_body: + self.unreachable_code, + name, + node, + last_node=node.body + if isinstance(node, ast.IfExp) + else node.body[-1], + message=f"unsatisfiable '{name}' condition", + confidence=100, + ) + elif utils.condition_is_always_true(node.test): + else_body = node.orelse + if name == "ternary": self._define( - self.unreachable_code, 'else', else_body[0], + self.unreachable_code, + name, + else_body, + message="unreachable 'else' expression", + confidence=100, + ) + elif else_body: + self._define( + self.unreachable_code, + "else", + else_body[0], last_node=else_body[-1], message="unreachable 'else' block", - confidence=100) + confidence=100, + ) + elif name == "if": + # Redundant if-condition without else block. + self._define( + self.unreachable_code, + name, + node, + message="redundant if-condition", + confidence=100, + ) - def _define(self, collection, name, first_node, last_node=None, - message='', confidence=DEFAULT_CONFIDENCE, ignore=None): + def _define( + self, + collection, + name, + first_node, + last_node=None, + message="", + confidence=DEFAULT_CONFIDENCE, + ignore=None, + ): + def ignored(lineno): + return ( + (ignore and ignore(self.filename, name)) + or _match(name, self.ignore_names) + or noqa.ignore_line(self.noqa_lines, lineno, ERROR_CODES[typ]) + ) + last_node = last_node or first_node typ = collection.typ - if (ignore and ignore(self.filename, name)) or _match( - name, self.ignore_names): - self._log('Ignoring {typ} "{name}"'.format(**locals())) + first_lineno = lines.get_first_line_number(first_node) + + if ignored(first_lineno): + self._log(f'Ignoring {typ} "{name}"') else: - first_lineno = first_node.lineno last_lineno = lines.get_last_line_number(last_node) collection.append( - Item(name, typ, self.filename, first_lineno, last_lineno, - message=message, confidence=confidence)) + Item( + name, + typ, + self.filename, + first_lineno, + last_lineno, + message=message, + confidence=confidence, + ) + ) def _define_variable(self, name, node, confidence=DEFAULT_CONFIDENCE): - self._define(self.defined_vars, name, node, confidence=confidence, - ignore=_ignore_variable) + self._define( + self.defined_vars, + name, + node, + confidence=confidence, + ignore=_ignore_variable, + ) def visit_arg(self, node): - """Function argument. - - ast.arg was added in Python 3.0. - ast.arg.lineno was added in Python 3.4. - """ + """Function argument""" self._define_variable(node.arg, node, confidence=100) def visit_AsyncFunctionDef(self, node): @@ -388,107 +478,170 @@ if isinstance(node.ctx, ast.Store): self._define(self.defined_attrs, node.attr, node) elif isinstance(node.ctx, ast.Load): - self.used_attrs.add(node.attr) + self.used_names.add(node.attr) + + def visit_BinOp(self, node): + """ + Parse variable names in old format strings: + + "%(my_var)s" % locals() + """ + if ( + isinstance(node.left, ast.Str) + and isinstance(node.op, ast.Mod) + and self._is_locals_call(node.right) + ): + self.used_names |= set(re.findall(r"%\((\w+)\)", node.left.s)) + + def visit_Call(self, node): + # Count getattr/hasattr(x, "some_attr", ...) as usage of some_attr. + if isinstance(node.func, ast.Name) and ( + (node.func.id == "getattr" and 2 <= len(node.args) <= 3) + or (node.func.id == "hasattr" and len(node.args) == 2) + ): + attr_name_arg = node.args[1] + if isinstance(attr_name_arg, ast.Str): + self.used_names.add(attr_name_arg.s) + + # Parse variable names in new format strings: + # "{my_var}".format(**locals()) + if ( + isinstance(node.func, ast.Attribute) + and isinstance(node.func.value, ast.Str) + and node.func.attr == "format" + and any( + kw.arg is None and self._is_locals_call(kw.value) + for kw in node.keywords + ) + ): + self._handle_new_format_string(node.func.value.s) + + def _handle_new_format_string(self, s): + def is_identifier(name): + return bool(re.match(r"[a-zA-Z_][a-zA-Z0-9_]*", name)) + + parser = string.Formatter() + try: + names = [name for _, name, _, _ in parser.parse(s) if name] + except ValueError: + # Invalid format string. + names = [] + + for field_name in names: + # Remove brackets and their contents: "a[0][b].c[d].e" -> "a.c.e", + # then split the resulting string: "a.b.c" -> ["a", "b", "c"] + vars = re.sub(r"\[\w*\]", "", field_name).split(".") + for var in vars: + if is_identifier(var): + self.used_names.add(var) + + @staticmethod + def _is_locals_call(node): + """Return True if the node is `locals()`.""" + return ( + isinstance(node, ast.Call) + and isinstance(node.func, ast.Name) + and node.func.id == "locals" + and not node.args + and not node.keywords + ) def visit_ClassDef(self, node): for decorator in node.decorator_list: - if _match(utils.get_decorator_name(decorator), - self.ignore_decorators): - self._log('Ignoring class "{}" (decorator whitelisted)'.format( - node.name)) + if _match( + utils.get_decorator_name(decorator), self.ignore_decorators + ): + self._log( + f'Ignoring class "{node.name}" (decorator whitelisted)' + ) break else: self._define( - self.defined_classes, node.name, node, ignore=_ignore_class) + self.defined_classes, node.name, node, ignore=_ignore_class + ) def visit_FunctionDef(self, node): - decorator_names = [utils.get_decorator_name( - decorator) for decorator in node.decorator_list] - typ = 'property' if '@property' in decorator_names else 'function' - if any(_match(name, self.ignore_decorators) - for name in decorator_names): - self._log('Ignoring {} "{}" (decorator whitelisted)'.format( - typ, node.name)) - elif typ == 'property': + decorator_names = [ + utils.get_decorator_name(decorator) + for decorator in node.decorator_list + ] + + first_arg = node.args.args[0].arg if node.args.args else None + + if "@property" in decorator_names: + typ = "property" + elif ( + "@staticmethod" in decorator_names + or "@classmethod" in decorator_names + or first_arg == "self" + ): + typ = "method" + else: + typ = "function" + + if any( + _match(name, self.ignore_decorators) for name in decorator_names + ): + self._log(f'Ignoring {typ} "{node.name}" (decorator whitelisted)') + elif typ == "property": self._define(self.defined_props, node.name, node) - elif '@pyqtSlot' in decorator_names or '@Slot' in decorator_names: - # @pyqtSlot and @Slot support - eric6 - self._define(self.defined_slots, node.name, node) - else: - # Function is not a property. + elif typ == "method": self._define( - self.defined_funcs, node.name, node, - ignore=_ignore_function) - - # Detect *args and **kwargs parameters. Python 3 recognizes them - # in visit_Name. For Python 2 we use this workaround. We can't - # use visit_arguments, because its node has no lineno. - for param in [node.args.vararg, node.args.kwarg]: - if param and isinstance(param, str): - self._define_variable(param, node, confidence=100) + self.defined_methods, node.name, node, ignore=_ignore_method + ) + else: + self._define( + self.defined_funcs, node.name, node, ignore=_ignore_function + ) def visit_If(self, node): - self._handle_conditional_node(node, 'if') + self._handle_conditional_node(node, "if") + + def visit_IfExp(self, node): + self._handle_conditional_node(node, "ternary") def visit_Import(self, node): self._add_aliases(node) def visit_ImportFrom(self, node): - if node.module != '__future__': + if node.module != "__future__": self._add_aliases(node) def visit_Name(self, node): - if (isinstance(node.ctx, ast.Load) and - node.id not in IGNORED_VARIABLE_NAMES): + if ( + isinstance(node.ctx, ast.Load) + and node.id not in IGNORED_VARIABLE_NAMES + ): self.used_names.add(node.id) elif isinstance(node.ctx, (ast.Param, ast.Store)): self._define_variable(node.id, node) - def visit_Str(self, node): - """ - Parse variable names in format strings: - - '%(my_var)s' % locals() - '{my_var}'.format(**locals()) - - """ - # Old format strings. - self.used_names |= set(re.findall(r'\%\((\w+)\)', node.s)) - - def is_identifier(s): - return bool(re.match(r'[a-zA-Z_][a-zA-Z0-9_]*', s)) - - # New format strings. - parser = string.Formatter() - try: - names = [name for _, name, _, _ in parser.parse(node.s) if name] - except ValueError: - # Invalid format string. - names = [] - - for field_name in names: - # Remove brackets and contents: "a[0][b].c[d].e" -> "a.c.e". - # "a.b.c" -> name = "a", attributes = ["b", "c"] - name_and_attrs = re.sub(r'\[\w*\]', '', field_name).split('.') - name = name_and_attrs[0] - if is_identifier(name): - self.used_names.add(name) - for attr in name_and_attrs[1:]: - if is_identifier(attr): - self.used_attrs.add(attr) - def visit_While(self, node): - self._handle_conditional_node(node, 'while') + self._handle_conditional_node(node, "while") def visit(self, node): - method = 'visit_' + node.__class__.__name__ + method = "visit_" + node.__class__.__name__ visitor = getattr(self, method, None) if self.verbose: - lineno = getattr(node, 'lineno', 1) - line = self.code[lineno - 1] if self.code else '' + lineno = getattr(node, "lineno", 1) + line = self.code[lineno - 1] if self.code else "" self._log(lineno, ast.dump(node), line) if visitor: visitor(node) + + # There isn't a clean subset of node types that might have type + # comments, so just check all of them. + type_comment = getattr(node, "type_comment", None) + if type_comment is not None: + mode = ( + "func_type" + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) + else "eval" + ) + self.visit( + ast.parse(type_comment, filename="<type_comment>", mode=mode) + ) + return self.generic_visit(node) def _handle_ast_list(self, ast_list): @@ -496,8 +649,9 @@ Find unreachable nodes in the given sequence of ast nodes. """ for index, node in enumerate(ast_list): - if isinstance(node, (ast.Break, ast.Continue, ast.Raise, - ast.Return)): + if isinstance( + node, (ast.Break, ast.Continue, ast.Raise, ast.Return) + ): try: first_unreachable_node = ast_list[index + 1] except IndexError: @@ -508,9 +662,9 @@ class_name, first_unreachable_node, last_node=ast_list[-1], - message="unreachable code after '{class_name}'".format( - **locals()), - confidence=100) + message=f"unreachable code after '{class_name}'", + confidence=100, + ) return def generic_visit(self, node): @@ -525,54 +679,18 @@ self.visit(value) -def _parse_args(): - def csv(exclude): - return exclude.split(',') - - usage = "%(prog)s [options] PATH [PATH ...]" - version = "vulture {0}".format(__version__) - glob_help = 'Patterns may contain glob wildcards (*, ?, [abc], [!abc]).' - parser = argparse.ArgumentParser(prog='vulture', usage=usage) - parser.add_argument( - 'paths', nargs='+', metavar='PATH', - help='Paths may be Python files or directories. For each directory' - ' Vulture analyzes all contained *.py files.') - parser.add_argument( - '--exclude', metavar='PATTERNS', type=csv, - help='Comma-separated list of paths to ignore (e.g.,' - ' "*settings.py,docs/*.py"). {glob_help} A PATTERN without glob' - ' wildcards is treated as *PATTERN*.'.format(**locals())) - parser.add_argument( - '--ignore-decorators', metavar='PATTERNS', type=csv, - help='Comma-separated list of decorators. Functions and classes using' - ' these decorators are ignored (e.g., "@app.route,@require_*").' - ' {glob_help}'.format(**locals())) - parser.add_argument( - '--ignore-names', metavar='PATTERNS', type=csv, default=None, - help='Comma-separated list of names to ignore (e.g., "visit_*,do_*").' - ' {glob_help}'.format(**locals())) - parser.add_argument( - '--make-whitelist', action='store_true', - help='Report unused code in a format that can be added to a' - ' whitelist module.') - parser.add_argument( - '--min-confidence', type=int, default=0, - help='Minimum confidence (between 0 and 100) for code to be' - ' reported as unused.') - parser.add_argument( - "--sort-by-size", action="store_true", - help='Sort unused functions and classes by their lines of code.') - parser.add_argument('-v', '--verbose', action='store_true') - parser.add_argument('--version', action='version', version=version) - return parser.parse_args() - - def main(): - args = _parse_args() - vulture = Vulture(verbose=args.verbose, ignore_names=args.ignore_names, - ignore_decorators=args.ignore_decorators) - vulture.scavenge(args.paths, exclude=args.exclude) - sys.exit(vulture.report( - min_confidence=args.min_confidence, - sort_by_size=args.sort_by_size, - make_whitelist=args.make_whitelist)) + config = make_config() + vulture = Vulture( + verbose=config["verbose"], + ignore_names=config["ignore_names"], + ignore_decorators=config["ignore_decorators"], + ) + vulture.scavenge(config["paths"], exclude=config["exclude"]) + sys.exit( + vulture.report( + min_confidence=config["min_confidence"], + sort_by_size=config["sort_by_size"], + make_whitelist=config["make_whitelist"], + ) + )
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/lines.py --- a/VultureChecker/vulture/lines.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/vulture/lines.py Sun Apr 25 16:13:53 2021 +0200 @@ -1,4 +1,5 @@ import ast +import sys def _get_last_child_with_lineno(node): @@ -15,11 +16,11 @@ list of nodes, we return the last one. """ - ignored_fields = set(['ctx', 'decorator_list', 'names', 'returns']) + ignored_fields = {"ctx", "decorator_list", "names", "returns"} fields = node._fields # The fields of ast.Call are in the wrong order. if isinstance(node, ast.Call): - fields = ('func', 'args', 'starargs', 'keywords', 'kwargs') + fields = ("func", "args", "starargs", "keywords", "kwargs") for name in reversed(fields): if name in ignored_fields: continue @@ -63,3 +64,20 @@ except AttributeError: pass node = last_child + + +def get_first_line_number(node): + """ + From Python 3.8 onwards, lineno for decorated objects is the line at which + the object definition starts, which is different from what Python < 3.8 + reported -- the lineno of the first decorator. To preserve this behaviour + of Vulture for newer Python versions, which is also more accurate for + counting the size of the unused code chunk (if the property is unused, we + also don't need it's decorators), we return the lineno of the first + decorator, if there are any. + """ + if sys.version_info >= (3, 8): + decorators = getattr(node, "decorator_list", []) + if decorators: + return decorators[0].lineno + return node.lineno
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/noqa.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VultureChecker/vulture/noqa.py Sun Apr 25 16:13:53 2021 +0200 @@ -0,0 +1,44 @@ +from collections import defaultdict +import re + +NOQA_REGEXP = re.compile( + # Use the same regex as flake8 does. + # https://gitlab.com/pycqa/flake8/-/tree/master/src/flake8/defaults.py + # We're looking for items that look like this: + # `# noqa` + # `# noqa: E123` + # `# noqa: E123,W451,F921` + # `# NoQA: E123,W451,F921` + r"# noqa(?::[\s]?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?", + re.IGNORECASE, +) + +NOQA_CODE_MAP = { + # flake8 F401: module imported but unused. + "F401": "V104", + # flake8 F841: local variable is assigned to but never used. + "F841": "V107", +} + + +def _parse_error_codes(match): + # If no error code is specified, add the line to the "all" category. + return [ + c.strip() for c in (match.groupdict()["codes"] or "all").split(",") + ] + + +def parse_noqa(code): + noqa_lines = defaultdict(set) + for lineno, line in enumerate(code, start=1): + match = NOQA_REGEXP.search(line) + if match: + for error_code in _parse_error_codes(match): + error_code = NOQA_CODE_MAP.get(error_code, error_code) + noqa_lines[error_code].add(lineno) + return noqa_lines + + +def ignore_line(noqa_lines, lineno, error_code): + """Check if the reported line is annotated with "# noqa".""" + return lineno in noqa_lines[error_code] or lineno in noqa_lines["all"]
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/utils.py --- a/VultureChecker/vulture/utils.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/vulture/utils.py Sun Apr 25 16:13:53 2021 +0200 @@ -1,12 +1,8 @@ import ast -import codecs import os import sys import tokenize -# Encoding to use when converting input files to unicode. -ENCODING = 'utf-8' - class VultureInputException(Exception): pass @@ -50,10 +46,11 @@ def format_path(path): - if not path: + try: + return path.relative_to(os.curdir) + except ValueError: + # Path is not below the current directory. return path - relpath = os.path.relpath(path) - return relpath if not relpath.startswith('..') else path def get_decorator_name(decorator): @@ -64,46 +61,39 @@ parts.append(decorator.attr) decorator = decorator.value parts.append(decorator.id) - return '@' + '.'.join(reversed(parts)) + return "@" + ".".join(reversed(parts)) -def get_modules(paths, toplevel=True): - """Take files from the command line even if they don't end with .py.""" +def get_modules(paths): + """Retrieve Python files to check. + + Loop over all given paths, abort if any ends with .pyc and add collect + the other given files (even those not ending with .py) and all .py + files under the given directories. + + """ modules = [] for path in paths: - path = os.path.abspath(path) - if toplevel and path.endswith('.pyc'): - sys.exit('.pyc files are not supported: {0}'.format(path)) - if os.path.isfile(path) and (path.endswith('.py') or toplevel): - modules.append(path) - elif os.path.isdir(path): - subpaths = [ - os.path.join(path, filename) - for filename in sorted(os.listdir(path))] - modules.extend(get_modules(subpaths, toplevel=False)) - elif toplevel: - sys.exit('Error: {0} could not be found.'.format(path)) + path = path.resolve() + if path.is_file(): + if path.suffix == ".pyc": + sys.exit(f"Error: *.pyc files are not supported: {path}") + else: + modules.append(path) + elif path.is_dir(): + modules.extend(path.rglob("*.py")) + else: + sys.exit(f"Error: {path} could not be found.") return modules def read_file(filename): - # Python >= 3.2 try: # Use encoding detected by tokenize.detect_encoding(). with tokenize.open(filename) as f: return f.read() except (SyntaxError, UnicodeDecodeError) as err: raise VultureInputException(err) - except AttributeError: - # tokenize.open was added in Python 3.2. - pass - - # Python < 3.2 - try: - with codecs.open(filename, encoding=ENCODING) as f: - return f.read() - except UnicodeDecodeError as err: - raise VultureInputException(err) class LoggingList(list): @@ -114,7 +104,7 @@ def append(self, item): if self._verbose: - print('define {0} "{1}"'.format(self.typ, item.name)) + print(f'define {self.typ} "{item.name}"') list.append(self, item) @@ -126,5 +116,5 @@ def add(self, name): if self._verbose: - print('use {0} "{1}"'.format(self.typ, name)) + print(f'use {self.typ} "{name}"') set.add(self, name)
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/version.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VultureChecker/vulture/version.py Sun Apr 25 16:13:53 2021 +0200 @@ -0,0 +1,1 @@ +__version__ = "2.3"
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/whitelists/argparse_whitelist.py --- a/VultureChecker/vulture/whitelists/argparse_whitelist.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/vulture/whitelists/argparse_whitelist.py Sun Apr 25 16:13:53 2021 +0200 @@ -1,7 +1,6 @@ import argparse -_ = argparse.ArgumentParser() -_.epilog +argparse.ArgumentParser().epilog -argparse.ArgumentDefaultsHelpFormatter._fill_text -argparse.ArgumentDefaultsHelpFormatter._get_help_string +argparse.ArgumentDefaultsHelpFormatter("prog")._fill_text +argparse.ArgumentDefaultsHelpFormatter("prog")._get_help_string
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/whitelists/ast_whitelist.py --- a/VultureChecker/vulture/whitelists/ast_whitelist.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/vulture/whitelists/ast_whitelist.py Sun Apr 25 16:13:53 2021 +0200 @@ -17,6 +17,7 @@ whitelist_node_visitor.visit_Call whitelist_node_visitor.visit_ClassDef whitelist_node_visitor.visit_Compare +whitelist_node_visitor.visit_Constant whitelist_node_visitor.visit_Delete whitelist_node_visitor.visit_Dict whitelist_node_visitor.visit_DictComp
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/whitelists/collections_whitelist.py --- a/VultureChecker/vulture/whitelists/collections_whitelist.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/vulture/whitelists/collections_whitelist.py Sun Apr 25 16:13:53 2021 +0200 @@ -1,4 +1,4 @@ import collections # To free memory, the "default_factory" attribute can be set to None. -collections.defaultdict.default_factory +collections.defaultdict().default_factory
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/whitelists/ctypes_whitelist.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VultureChecker/vulture/whitelists/ctypes_whitelist.py Sun Apr 25 16:13:53 2021 +0200 @@ -0,0 +1,8 @@ +from ctypes import _CFuncPtr +from ctypes import _Pointer + +_CFuncPtr.argtypes +_CFuncPtr.errcheck +_CFuncPtr.restype + +_Pointer.contents
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/whitelists/logging_whitelist.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VultureChecker/vulture/whitelists/logging_whitelist.py Sun Apr 25 16:13:53 2021 +0200 @@ -0,0 +1,5 @@ +import logging + +logging.Filter.filter +logging.getLogger().propagate +logging.StreamHandler.emit
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/whitelists/string_whitelist.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VultureChecker/vulture/whitelists/string_whitelist.py Sun Apr 25 16:13:53 2021 +0200 @@ -0,0 +1,10 @@ +import string + +string.Formatter.check_unused_args +string.Formatter.convert_field +string.Formatter.format +string.Formatter.format_field +string.Formatter.get_field +string.Formatter.get_value +string.Formatter.parse +string.Formatter.vformat
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/whitelists/sys_whitelist.py --- a/VultureChecker/vulture/whitelists/sys_whitelist.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/vulture/whitelists/sys_whitelist.py Sun Apr 25 16:13:53 2021 +0200 @@ -1,5 +1,7 @@ import sys +sys.excepthook + # Never report redirected streams as unused. sys.stderr sys.stdin
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/whitelists/unittest_whitelist.py --- a/VultureChecker/vulture/whitelists/unittest_whitelist.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/vulture/whitelists/unittest_whitelist.py Sun Apr 25 16:13:53 2021 +0200 @@ -1,4 +1,4 @@ -from unittest import TestCase +from unittest import mock, TestCase TestCase.setUp TestCase.tearDown @@ -10,17 +10,7 @@ TestCase.failureException TestCase.longMessage TestCase.maxDiff -try: - # new in Python 3.4 - TestCase.subTest -except AttributeError: - pass +TestCase.subTest -try: - # unittest.mock was introduced in Python 3.3 - from unittest import mock -except ImportError: - pass -else: - mock.Mock.return_value - mock.Mock.side_effect +mock.Mock.return_value +mock.Mock.side_effect
diff -r d7a6b7ea640d -r 3c2922b45a9f VultureChecker/vulture/whitelists/whitelist_utils.py --- a/VultureChecker/vulture/whitelists/whitelist_utils.py Wed Dec 30 11:02:01 2020 +0100 +++ b/VultureChecker/vulture/whitelists/whitelist_utils.py Sun Apr 25 16:13:53 2021 +0200 @@ -25,6 +25,7 @@ correct, but can also be executed. """ + def __getattr__(self, _): pass