Repository: asottile/pyupgrade

Issue:
open() rewrite doesn't always consider other positional args
Minimal input:

```py
with open('blah.txt', 'r', 'utf-8') as fp:
    pass
```

Actual output:

```py
with open('blah.txt', 'utf-8') as fp:
    pass
```

Expected:

```py
with open('blah.txt', encoding='utf-8') as fp:
    pass
```

The rewrite causes the following Python error upon executing the code:

```
Traceback (most recent call last):
  File "blah.py", line 1, in <module>
    with open('blah.txt', 'utf-8') as fp:
ValueError: invalid mode: 'utf-8'
```

Discovered in a real code base:

https://github.com/andialbrecht/sqlparse/blob/28a2acdd9b307c95d2111f1f831b1a5afc634691/sqlparse/cli.py#L175


Discussion:

Patch:
diff --git a/pyupgrade.py b/pyupgrade.py
index a49ef68..45a13bb 100644
--- a/pyupgrade.py
+++ b/pyupgrade.py
@@ -1127,7 +1127,7 @@ SIX_NATIVE_STR = frozenset(('ensure_str', 'ensure_text', 'text_type'))
 U_MODE_REMOVE = frozenset(('U', 'Ur', 'rU', 'r', 'rt', 'tr'))
 U_MODE_REPLACE_R = frozenset(('Ub', 'bU'))
 U_MODE_REMOVE_U = frozenset(('rUb', 'Urb', 'rbU', 'Ubr', 'bUr', 'brU'))
-U_MODE_ALL = U_MODE_REMOVE | U_MODE_REPLACE_R | U_MODE_REMOVE_U
+U_MODE_REPLACE = U_MODE_REPLACE_R | U_MODE_REMOVE_U
 
 
 def _all_isinstance(
@@ -1570,10 +1570,12 @@ class FindPy3Plus(ast.NodeVisitor):
         elif (
                 isinstance(node.func, ast.Name) and
                 node.func.id == 'open' and
-                len(node.args) >= 2 and
                 not _starargs(node) and
-                isinstance(node.args[1], ast.Str) and
-                node.args[1].s in U_MODE_ALL
+                len(node.args) >= 2 and
+                isinstance(node.args[1], ast.Str) and (
+                    node.args[1].s in U_MODE_REPLACE or
+                    (len(node.args) == 2 and node.args[1].s in U_MODE_REMOVE)
+                )
         ):
             self.open_mode_calls.add(_ast_to_offset(node))
         elif (


Test patch:
diff --git a/tests/open_mode_test.py b/tests/open_mode_test.py
index dd306a8..1398792 100644
--- a/tests/open_mode_test.py
+++ b/tests/open_mode_test.py
@@ -13,6 +13,8 @@ from pyupgrade import _fix_py3_plus
         'open("foo", "Uw")',
         # TODO: could maybe be rewritten to remove t?
         'open("foo", "wt")',
+        # don't remove this, they meant to use `encoding=`
+        'open("foo", "r", "utf-8")',
     ),
 )
 def test_fix_open_mode_noop(s):