Wed, 28 Oct 2015 19:47:16 +0100
Redid the future imports checker.
--- a/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py Tue Oct 27 19:46:12 2015 +0100 +++ b/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py Wed Oct 28 19:47:16 2015 +0100 @@ -40,6 +40,10 @@ codeRole = Qt.UserRole + 6 ignoredRole = Qt.UserRole + 7 + availableFutures = [ + 'division', 'absolute_import', 'with_statement', + 'print_function', 'unicode_literals', 'generator_stop'] + def __init__(self, styleCheckService, parent=None): """ Constructor @@ -64,6 +68,8 @@ self.docTypeComboBox.addItem(self.tr("PEP-257"), "pep257") self.docTypeComboBox.addItem(self.tr("Eric"), "eric") + self.futuresList.addItems(self.availableFutures) + self.statisticsButton = self.buttonBox.addButton( self.tr("Statistics..."), QDialogButtonBox.ActionRole) self.statisticsButton.setToolTip( @@ -307,6 +313,8 @@ "CopyrightAuthor" not in self.__data: self.__data["CopyrightMinFileSize"] = 0 self.__data["CopyrightAuthor"] = "" + if "FutureChecker" not in self.__data: + self.__data["FutureChecker"] = "" self.excludeFilesEdit.setText(self.__data["ExcludeFiles"]) self.excludeMessagesEdit.setText(self.__data["ExcludeMessages"]) @@ -325,6 +333,7 @@ self.copyrightFileSizeSpinBox.setValue( self.__data["CopyrightMinFileSize"]) self.copyrightAuthorEdit.setText(self.__data["CopyrightAuthor"]) + self.__initFuturesList(self.__data["FutureChecker"]) def start(self, fn, save=False, repeat=None): """ @@ -409,7 +418,8 @@ "CopyrightChecker": { "MinFilesize": self.copyrightFileSizeSpinBox.value(), "Author": self.copyrightAuthorEdit.text(), - } + }, + "FutureChecker": self.__getSelectedFutureImports(), } self.__options = [excludeMessages, includeMessages, repeatMessages, @@ -698,6 +708,7 @@ "ValidEncodings": self.encodingsEdit.text(), "CopyrightMinFileSize": self.copyrightFileSizeSpinBox.value(), "CopyrightAuthor": self.copyrightAuthorEdit.text(), + "FutureChecker": self.__getSelectedFutureImports(), } if data != self.__data: self.__data = data @@ -848,25 +859,25 @@ Private slot to load the default configuration values. """ self.excludeFilesEdit.setText(Preferences.Prefs.settings.value( - "PEP8/ExcludeFilePatterns")) + "PEP8/ExcludeFilePatterns", "")) self.excludeMessagesEdit.setText(Preferences.Prefs.settings.value( "PEP8/ExcludeMessages", pep8.DEFAULT_IGNORE)) self.includeMessagesEdit.setText(Preferences.Prefs.settings.value( - "PEP8/IncludeMessages")) + "PEP8/IncludeMessages", "")) self.repeatCheckBox.setChecked(Preferences.toBool( - Preferences.Prefs.settings.value("PEP8/RepeatMessages"))) + Preferences.Prefs.settings.value("PEP8/RepeatMessages", False))) self.fixIssuesEdit.setText(Preferences.Prefs.settings.value( - "PEP8/FixCodes")) + "PEP8/FixCodes", "")) self.noFixIssuesEdit.setText(Preferences.Prefs.settings.value( "PEP8/NoFixCodes", "E501")) self.fixIssuesCheckBox.setChecked(Preferences.toBool( - Preferences.Prefs.settings.value("PEP8/FixIssues"))) + Preferences.Prefs.settings.value("PEP8/FixIssues", False))) self.ignoredCheckBox.setChecked(Preferences.toBool( - Preferences.Prefs.settings.value("PEP8/ShowIgnored"))) + Preferences.Prefs.settings.value("PEP8/ShowIgnored", False))) self.lineLengthSpinBox.setValue(int(Preferences.Prefs.settings.value( "PEP8/MaxLineLength", pep8.MAX_LINE_LENGTH))) self.hangClosingCheckBox.setChecked(Preferences.toBool( - Preferences.Prefs.settings.value("PEP8/HangClosing"))) + Preferences.Prefs.settings.value("PEP8/HangClosing", False))) self.docTypeComboBox.setCurrentIndex(self.docTypeComboBox.findData( Preferences.Prefs.settings.value("PEP8/DocstringType", "pep257"))) self.complexitySpinBox.setValue(int(Preferences.Prefs.settings.value( @@ -876,7 +887,9 @@ self.copyrightFileSizeSpinBox.setValue(int( Preferences.Prefs.settings.value("PEP8/CopyrightMinFileSize", 0))) self.copyrightAuthorEdit.setText( - Preferences.Prefs.settings.value("PEP8/CopyrightAuthor")) + Preferences.Prefs.settings.value("PEP8/CopyrightAuthor", "")) + self.__initFuturesList( + Preferences.Prefs.settings.value("PEP8/FutureChecker", "")) @pyqtSlot() def on_storeDefaultButton_clicked(self): @@ -915,6 +928,8 @@ "PEP8/CopyrightMinFileSize", self.copyrightFileSizeSpinBox.value()) Preferences.Prefs.settings.setValue( "PEP8/CopyrightAuthor", self.copyrightAuthorEdit.text()) + Preferences.Prefs.settings.setValue( + "PEP8/FutureChecker", self.__getSelectedFutureImports()) @pyqtSlot() def on_resetDefaultButton_clicked(self): @@ -939,6 +954,7 @@ "PEP8/ValidEncodings", "latin-1, utf-8") Preferences.Prefs.settings.setValue("PEP8/CopyrightMinFileSize", 0) Preferences.Prefs.settings.setValue("PEP8/CopyrightAuthor", "") + Preferences.Prefs.settings.setValue("PEP8/FutureChecker", "") @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): @@ -1038,3 +1054,30 @@ """ return (itm.data(0, self.fixableRole) and not itm.data(0, self.ignoredRole)) + + def __initFuturesList(self, selectedFutures): + """ + Private method to set the selected status of the future imports. + + @param selectedFutures comma separated list of expected future imports + @type str + """ + if selectedFutures: + expectedImports = [ + i.strip() for i in selectedFutures.split(",") + if bool(i.strip())] + else: + expectedImports = [] + for row in range(self.futuresList.count()): + itm = self.futuresList.item(row) + itm.setSelected(itm.text() in expectedImports) + + def __getSelectedFutureImports(self): + """ + Private method to get the expected future imports. + + @return expected future imports as a comma separated string + @rtype str + """ + selectedFutures = [i.text() for i in self.futuresList.selectedItems()] + return ", ".join(selectedFutures)
--- a/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.ui Tue Oct 27 19:46:12 2015 +0100 +++ b/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.ui Wed Oct 28 19:47:16 2015 +0100 @@ -208,7 +208,7 @@ <attribute name="title"> <string>Specific Options</string> </attribute> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="verticalLayout_4"> <property name="leftMargin"> <number>1</number> </property> @@ -239,12 +239,12 @@ <property name="geometry"> <rect> <x>0</x> - <y>0</y> - <width>529</width> - <height>440</height> + <y>-160</y> + <width>542</width> + <height>473</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout_4"> + <layout class="QVBoxLayout" name="verticalLayout_2"> <item> <widget class="QGroupBox" name="groupBox"> <property name="title"> @@ -423,6 +423,38 @@ </widget> </item> <item> + <widget class="QGroupBox" name="groupBox_6"> + <property name="title"> + <string>Future Imports</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string>Expected Imports:</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item> + <widget class="QListWidget" name="futuresList"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <widget class="QGroupBox" name="groupBox_3"> <property name="title"> <string>McCabe Complexity</string> @@ -666,6 +698,9 @@ <tabstop>hangClosingCheckBox</tabstop> <tabstop>docTypeComboBox</tabstop> <tabstop>encodingsEdit</tabstop> + <tabstop>copyrightFileSizeSpinBox</tabstop> + <tabstop>copyrightAuthorEdit</tabstop> + <tabstop>futuresList</tabstop> <tabstop>complexitySpinBox</tabstop> <tabstop>startButton</tabstop> <tabstop>fixButton</tabstop>
--- a/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py Tue Oct 27 19:46:12 2015 +0100 +++ b/Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py Wed Oct 28 19:47:16 2015 +0100 @@ -23,8 +23,7 @@ "M111", "M112", "M121", "M131", - "M701", "M702", "M703", "M704", "M705", "M706", - "M721", "M722", "M723", "M724", "M725", "M726", + "M701", "M702", "M801", "M811", @@ -64,16 +63,6 @@ self.__pep3101FormatRegex = re.compile( r'^(?:[^\'"]*[\'"][^\'"]*[\'"])*\s*%|^\s*%') - self.__availableFutureImports = { - # future import: (code missing, code present) - 'division': ("M701", "M721"), - 'absolute_import': ("M702", "M722"), - 'with_statement': ("M703", "M723"), - 'print_function': ("M704", "M724"), - 'unicode_literals': ("M705", "M725"), - 'generator_stop': ("M706", "M726"), - } - # statistics counters self.counters = {} @@ -88,9 +77,7 @@ (self.__checkPep3101, ("M131",)), (self.__checkPrintStatements, ("M801",)), (self.__checkTuple, ("M811", )), - (self.__checkFuture, ("M701", "M702", "M703", "M704", "M705", - "M706", "M721", "M722", "M723", "M724", - "M725", "M726")), + (self.__checkFuture, ("M701", "M702")), ] self.__defaultArgs = { @@ -296,72 +283,34 @@ """ Private method to check the __future__ imports. """ - visitor = FutureImportVisitor() - visitor.visit(self.__tree) - if not visitor.hasCode: + expectedImports = set( + [i.strip() + for i in self.__args.get("FutureChecker", "").split(",") + if bool(i.strip())]) + if len(expectedImports) == 0: + # nothing to check for; disabling the check return - present = set() - for importNode in visitor.futureImports: - for alias in importNode.names: - if alias.name not in self.__availableFutureImports: - # unknown code - continue - self.__error(importNode.lineno - 1, 0, - self.__availableFutureImports[alias.name][1]) - present.add(alias.name) - for name in self.__availableFutureImports: - if name not in present: - self.__error(0, 0, - self.__availableFutureImports[name][0]) - - -class FutureImportVisitor(ast.NodeVisitor): - """ - Class implementing a node visitor to look for __future__ imports. - """ - def __init__(self): - """ - Constructor - """ - super(FutureImportVisitor, self).__init__() - self.futureImports = [] - self.__hasCode = False - - def visit_ImportFrom(self, node): - """ - Public method to analyze an 'from ... import ...' node. + imports = set() + node = None - @param node reference to the ImportFrom node - @type ast.AST - """ - if node.module == '__future__': - self.futureImports += [node] - - def visit_Expr(self, node): - """ - Public method to analyze an expression node. - - @param node reference to the expression node - @type ast.AST - """ - if not isinstance(node.value, ast.Str) or node.value.col_offset != 0: - self.__hasCode = True + for node in ast.walk(self.__tree): + if (isinstance(node, ast.ImportFrom) and + node.module == '__future__'): + imports |= set(name.name for name in node.names) + elif isinstance(node, ast.Expr): + if not isinstance(node.value, ast.Str): + break + elif not isinstance(node, ast.Module): + break - def generic_visit(self, node): - """ - Public method to analyze any other node. - - @param node reference to the node - @type ast.AST - """ - if not isinstance(node, ast.Module): - self.__hasCode = True - super(FutureImportVisitor, self).generic_visit(node) + if isinstance(node, ast.Module): + return - @property - def hasCode(self): - """ - Public method to check for the presence of some code. - """ - return self.__hasCode or self.futureImports + if not (imports >= expectedImports): + if imports: + self.__error(node.lineno - 1, node.col_offset, "M701", + ", ".join(expectedImports), ", ".join(imports)) + else: + self.__error(node.lineno - 1, node.col_offset, "M702", + ", ".join(expectedImports))
--- a/Plugins/CheckerPlugins/CodeStyleChecker/translations.py Tue Oct 27 19:46:12 2015 +0100 +++ b/Plugins/CheckerPlugins/CodeStyleChecker/translations.py Wed Oct 28 19:47:16 2015 +0100 @@ -399,40 +399,10 @@ "found {0} formatter"), "M701": QCoreApplication.translate( "MiscellaneousChecker", - "__future__ import 'division' missing"), + "expected these __future__ imports: {0}; but only got: {1}"), "M702": QCoreApplication.translate( "MiscellaneousChecker", - "__future__ import 'absolute_import' missing"), - "M703": QCoreApplication.translate( - "MiscellaneousChecker", - "__future__ import 'with_statement' missing"), - "M704": QCoreApplication.translate( - "MiscellaneousChecker", - "__future__ import 'print_function' missing"), - "M705": QCoreApplication.translate( - "MiscellaneousChecker", - "__future__ import 'unicode_literals' missing"), - "M706": QCoreApplication.translate( - "MiscellaneousChecker", - "__future__ import 'generator_stop' missing"), - "M721": QCoreApplication.translate( - "MiscellaneousChecker", - "__future__ import 'division' present"), - "M722": QCoreApplication.translate( - "MiscellaneousChecker", - "__future__ import 'absolute_import' present"), - "M723": QCoreApplication.translate( - "MiscellaneousChecker", - "__future__ import 'with_statement' present"), - "M724": QCoreApplication.translate( - "MiscellaneousChecker", - "__future__ import 'print_function' present"), - "M725": QCoreApplication.translate( - "MiscellaneousChecker", - "__future__ import 'unicode_literals' present"), - "M726": QCoreApplication.translate( - "MiscellaneousChecker", - "__future__ import 'generator_stop' present"), + "expected these __future__ imports: {0}; but got none"), "M801": QCoreApplication.translate( "MiscellaneousChecker", "print statement found"), @@ -617,6 +587,8 @@ "C901": ["SyntaxError", "Invalid Syntax"], "M102": ["enc42"], "M131": ["%s"], + "M701": ["print_function, unicode_literals", "print_function"], + "M702": ["print_function, unicode_literals"], "M901": ["SyntaxError", "Invalid Syntax"], "FWRITE_ERROR": ["IOError"], }