Georg Brandl avatar Georg Brandl committed 088b407

#507: Fix crash parsing Python argument lists containing brackets in string literals.

Comments (0)

Files changed (3)

 Release 1.0.3 (in development)
 ==============================
 
+* #507: Fix crash parsing Python argument lists containing brackets
+  in string literals.
+
 * #501: Fix regression when building LaTeX docs with figures that
   don't have captions.
 

sphinx/domains/javascript.py

 from sphinx.domains import Domain, ObjType
 from sphinx.locale import l_, _
 from sphinx.directives import ObjectDescription
-from sphinx.domains.python import py_paramlist_re as js_paramlist_re
 from sphinx.roles import XRefRole
+from sphinx.domains.python import _pseudo_parse_arglist
 from sphinx.util.nodes import make_refnode
 from sphinx.util.docfields import Field, GroupedField, TypedField
 
             signode += addnodes.desc_addname(nameprefix + '.', nameprefix + '.')
         signode += addnodes.desc_name(name, name)
         if self.has_arguments:
-            signode += addnodes.desc_parameterlist()
-        if not arglist:
-            return fullname, nameprefix
-
-        stack = [signode[-1]]
-        for token in js_paramlist_re.split(arglist):
-            if token == '[':
-                opt = addnodes.desc_optional()
-                stack[-1] += opt
-                stack.append(opt)
-            elif token == ']':
-                try:
-                    stack.pop()
-                except IndexError:
-                    raise ValueError()
-            elif not token or token == ',' or token.isspace():
-                pass
+            if not arglist:
+                signode += addnodes.desc_parameterlist()
             else:
-                token = token.strip()
-                stack[-1] += addnodes.desc_parameter(token, token)
-        if len(stack) != 1:
-            raise ValueError()
+                _pseudo_parse_arglist(signode, arglist)
         return fullname, nameprefix
 
     def add_target_and_index(self, name_obj, sig, signode):

sphinx/domains/python.py

           )? $                   # and nothing more
           ''', re.VERBOSE)
 
-py_paramlist_re = re.compile(r'([\[\],])')  # split at '[', ']' and ','
+
+def _pseudo_parse_arglist(signode, arglist):
+    """"Parse" a list of arguments separated by commas.
+
+    Arguments can have "optional" annotations given by enclosing them in
+    brackets.  Currently, this will split at any comma, even if it's inside a
+    string literal (e.g. default argument value).
+    """
+    paramlist = addnodes.desc_parameterlist()
+    stack = [paramlist]
+    try:
+        for argument in arglist.split(','):
+            argument = argument.strip()
+            ends_open = ends_close = 0
+            while argument.startswith('['):
+                stack.append(addnodes.desc_optional())
+                stack[-2] += stack[-1]
+                argument = argument[1:].strip()
+            while argument.startswith(']'):
+                stack.pop()
+                argument = argument[1:].strip()
+            while argument.endswith(']'):
+                ends_close += 1
+                argument = argument[:-1].strip()
+            while argument.endswith('['):
+                ends_open += 1
+                argument = argument[:-1].strip()
+            if argument:
+                stack[-1] += addnodes.desc_parameter(argument, argument)
+            while ends_open:
+                stack.append(addnodes.desc_optional())
+                stack[-2] += stack[-1]
+                ends_open -= 1
+            while ends_close:
+                stack.pop()
+                ends_close -= 1
+        if len(stack) != 1:
+            raise IndexError
+    except IndexError:
+        # if there are too few or too many elements on the stack, just give up
+        # and treat the whole argument list as one argument, discarding the
+        # already partially populated paramlist node
+        signode += addnodes.desc_parameterlist()
+        signode[-1] += addnodes.desc_parameter(arglist, arglist)
+    else:
+        signode += paramlist
 
 
 class PyObject(ObjectDescription):
             if retann:
                 signode += addnodes.desc_returns(retann, retann)
             return fullname, name_prefix
-        signode += addnodes.desc_parameterlist()
-
-        stack = [signode[-1]]
-        for token in py_paramlist_re.split(arglist):
-            if token == '[':
-                opt = addnodes.desc_optional()
-                stack[-1] += opt
-                stack.append(opt)
-            elif token == ']':
-                try:
-                    stack.pop()
-                except IndexError:
-                    raise ValueError
-            elif not token or token == ',' or token.isspace():
-                pass
-            else:
-                token = token.strip()
-                stack[-1] += addnodes.desc_parameter(token, token)
-        if len(stack) != 1:
-            raise ValueError
+        _pseudo_parse_arglist(signode, arglist)
         if retann:
             signode += addnodes.desc_returns(retann, retann)
         return fullname, name_prefix
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.