Plugins/CheckerPlugins/Pep8/Pep257Checker.py

changeset 2929
28ab0bc63d69
parent 2928
4f74d3f595ce
child 2934
82811ddafea2
diff -r 4f74d3f595ce -r 28ab0bc63d69 Plugins/CheckerPlugins/Pep8/Pep257Checker.py
--- a/Plugins/CheckerPlugins/Pep8/Pep257Checker.py	Sun Sep 22 19:47:04 2013 +0200
+++ b/Plugins/CheckerPlugins/Pep8/Pep257Checker.py	Mon Sep 23 19:32:25 2013 +0200
@@ -20,6 +20,7 @@
     from io import StringIO             # __IGNORE_WARNING__
 import tokenize
 import ast
+import sys
 
 from PyQt4.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
 
@@ -112,7 +113,7 @@
         
         "D203", "D205",
         "D221",
-        "D231", "D234", "D235", "D236", "D237",
+        "D231", "D234", "D235", "D236", "D237", "D238",
         "D242", "D243", "D244", "D245",
     ]
     
@@ -184,6 +185,9 @@
             "Pep257Checker",
             "docstring contains too many @param/@keyparam lines"),
         "D237": QT_TRANSLATE_NOOP(
+            "Pep257Checker",
+            "keyword only arguments must be documented with @keyparam lines"),
+        "D238": QT_TRANSLATE_NOOP(
             "Pep257Checker", "order of @param/@keyparam lines does"
             " not match the function/method signature"),
         "D242": QT_TRANSLATE_NOOP(
@@ -295,7 +299,7 @@
                     (self.__checkNoSignature, ("D133",)),
                     (self.__checkEricReturn, ("D234",)),
                     (self.__checkEricFunctionArguments,
-                     ("D235", "D236", "D237")),
+                     ("D235", "D236", "D237", "D238")),
                     (self.__checkNoBlankLineBefore, ("D141",)),
                 ],
                 "docstring": [
@@ -359,9 +363,10 @@
             # record the issue with one based line number
             self.errors.append((self.__filename, lineNumber + 1, offset, text))
     
-    def getMessage(self, code, *args):
+    @classmethod
+    def getMessage(cls, code, *args):
         """
-        Public method to get a translated and formatted message for a
+        Class method to get a translated and formatted message for a
         given code.
         
         @param code message code (string)
@@ -474,21 +479,56 @@
             summaries.append(line2)
         return summaries, lineno
     
-    def __getArgNames(self, node):
-        """
-        Private method to get the argument names of a function node.
-        
-        @param node AST node to extract arguments names from
-        @return list of argument names (list of string)
-        """
-        arguments = []
-        arguments.extend([arg.arg for arg in node.args.args])
-        if node.args.vararg is not None:
-            arguments.append(node.args.vararg)
-        arguments.extend([arg.arg for arg in node.args.kwonlyargs])
-        if node.args.kwarg is not None:
-            arguments.append(node.args.kwarg)
-        return arguments
+    if sys.version_info[0] < 3:
+        def __getArgNames(self, node):
+            """
+            Private method to get the argument names of a function node.
+            
+            @param node AST node to extract arguments names from
+            @return tuple of two list of argument names, one for arguments
+                and one for keyword arguments (tuple of list of string)
+            """
+            def unpackArgs(args):
+                """
+                Local helper function to unpack function argument names.
+                
+                @param args list of AST node arguments
+                @return list of argument names (list of string)
+                """
+                ret = []
+                for arg in args:
+                    if isinstance(arg, ast.Tuple):
+                        ret.extend(unpackArgs(arg.elts))
+                    else:
+                        ret.append(arg.id)
+                return ret
+            
+            arguments = unpackArgs(node.args.args)
+            if node.args.vararg is not None:
+                arguments.append(node.args.vararg)
+            kwarguments = []
+            if node.args.kwarg is not None:
+                kwarguments.append(node.args.kwarg)
+            return arguments, kwarguments
+    else:
+        def __getArgNames(self, node):          # __IGNORE_WARNING__
+            """
+            Private method to get the argument names of a function node.
+            
+            @param node AST node to extract arguments names from
+            @return tuple of two list of argument names, one for arguments
+                and one for keyword arguments (tuple of list of string)
+            """
+            arguments = []
+            arguments.extend([arg.arg for arg in node.args.args])
+            if node.args.vararg is not None:
+                arguments.append(node.args.vararg)
+            
+            kwarguments = []
+            kwarguments.extend([arg.arg for arg in node.args.kwonlyargs])
+            if node.args.kwarg is not None:
+                kwarguments.append(node.args.kwarg)
+            return arguments, kwarguments
     
     ##################################################################
     ## Parsing functionality below
@@ -1084,7 +1124,7 @@
         if (isinstance(tree, ast.Module) and len(tree.body) == 1 and
                 isinstance(tree.body[0], ast.FunctionDef)):
             functionDef = tree.body[0]
-            argNames = self.__getArgNames(functionDef)
+            argNames, kwNames = self.__getArgNames(functionDef)
             if "self" in argNames:
                 argNames.remove("self")
             if "cls" in argNames:
@@ -1092,12 +1132,29 @@
             
             docstring = docstringContext.ssource()
             if (docstring.count("@param") + docstring.count("@keyparam") < 
-                    len(argNames)):
+                    len(argNames + kwNames)):
                 self.__error(docstringContext.end(), 0, "D235")
             elif (docstring.count("@param") + docstring.count("@keyparam") > 
-                    len(argNames)):
+                    len(argNames + kwNames)):
                 self.__error(docstringContext.end(), 0, "D236")
-            # TODO: check order (args, vararg, kwonlyargs, kwarg
+            else:
+                # extract @param and @keyparam from docstring
+                args = []
+                kwargs = []
+                for line in docstringContext.source():
+                    if line.strip().startswith(("@param", "@keyparam")):
+                        at, name, _ = line.strip().split(None, 2)
+                        if at == "@keyparam":
+                            kwargs.append(name)
+                        args.append(name)
+                
+                # do the checks
+                for name in kwNames:
+                    if name not in kwargs:
+                        self.__error(docstringContext.end(), 0, "D237")
+                        return
+                if argNames + kwNames != args:
+                    self.__error(docstringContext.end(), 0, "D238")
     
     def __checkEricBlankAfterSummary(self, docstringContext, context):
         """

eric ide

mercurial