Redid the future imports checker.

Wed, 28 Oct 2015 19:47:16 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 28 Oct 2015 19:47:16 +0100
changeset 4509
7797ee4a45f9
parent 4508
a3b38825acf0
child 4510
43437fc9f4c9

Redid the future imports checker.

Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.py file | annotate | diff | comparison | revisions
Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleCheckerDialog.ui file | annotate | diff | comparison | revisions
Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py file | annotate | diff | comparison | revisions
Plugins/CheckerPlugins/CodeStyleChecker/translations.py file | annotate | diff | comparison | revisions
--- 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"],
 }

eric ide

mercurial