Repository: asottile/pyupgrade

Issue:
SyntaxError rewriting dict(thing,) or set(thing,) (trailing comma)
For example

```python
>>> set(x for x in range(1),)
{0}
>>> {x for x in range(1),}
  File "<stdin>", line 1
    {x for x in range(1),}
                        ^
SyntaxError: invalid syntax
```

Discussion:

Patch:
diff --git a/pyupgrade.py b/pyupgrade.py
index d2a6990..eafc8a3 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -46,6 +46,7 @@ def unparse_parsed_string(parsed):
 
 
 UNIMPORTANT_WS = 'UNIMPORTANT_WS'
+NON_CODING_TOKENS = frozenset(('COMMENT', 'NL', UNIMPORTANT_WS))
 Token = collections.namedtuple(
     'Token', ('name', 'src', 'line', 'utf8_byte_offset'),
 )
@@ -145,7 +146,7 @@ def _fix_format_literals(contents_text):
             to_replace.append((string_start, string_end))
             string_start, string_end, seen_dot = None, None, False
         # NL is the non-breaking newline token
-        elif token.name not in ('COMMENT', 'NL', UNIMPORTANT_WS):
+        elif token.name not in NON_CODING_TOKENS:
             string_start, string_end, seen_dot = None, None, False
 
     for start, end in reversed(to_replace):
@@ -291,6 +292,13 @@ def _get_victims(tokens, start, arg):
 
         i += 1
 
+    # May need to remove a trailing comma
+    i -= 2
+    while tokens[i].name in NON_CODING_TOKENS:
+        i -= 1
+    if tokens[i].src == ',':
+        ends = sorted(set(ends + [i]))
+
     return Victims(starts, ends, first_comma_index, arg_index)
 
 


Test patch:
diff --git a/tests/pyupgrade_test.py b/tests/pyupgrade_test.py
index b2ff30f..7b48712 100644
--- a/tests/pyupgrade_test.py
+++ b/tests/pyupgrade_test.py
@@ -130,6 +130,17 @@ def test_roundtrip_tokenize(filename):
         # Remove trailing commas on inline things
         ('set((1,))', '{1}'),
         ('set((1, ))', '{1}'),
+        # Remove trailing commas after things
+        ('set([1, 2, 3,],)', '{1, 2, 3}'),
+        ('set(x for x in y,)', '{x for x in y}'),
+        (
+            'set(\n'
+            '    x for x in y,\n'
+            ')',
+            '{\n'
+            '    x for x in y\n'
+            '}',
+        ),
     ),
 )
 def test_sets(s, expected):
@@ -167,6 +178,25 @@ def test_sets(s, expected):
         ),
         # This doesn't get fixed by autopep8 and can cause a syntax error
         ('dict((a, b)for a, b in y)', '{a: b for a, b in y}'),
+        # Need to remove trailing commas on the element
+        (
+            'dict(\n'
+            '    (\n'
+            '        a,\n'
+            '        b,\n'
+            '    )\n'
+            '    for a, b in y\n'
+            ')',
+            # Ideally, this'll go through some other formatting tool before
+            # being committed.  Shrugs!
+            '{\n'
+            '    \n'
+            '        a:\n'
+            '        b\n'
+            '    \n'
+            '    for a, b in y\n'
+            '}',
+        ),
     ),
 )
 def test_dictcomps(s, expected):