Repository: hhatto/autopep8

Issue:
autopep8 wrongly adds raw string 
autopep8 correctly fixes code from this:
        '^(\d*)x(\d*):(\d*)$'
to:
        r'^(\d*)x(\d*):(\d*)$'

The problem is when the string has both invalid and valid escape sequence.
Then autopep8 wrong adds r'...' , which breaks legacy code.
I think autopep8 shouldn't add r'...' in cases when you have both  invalid and valid escape sequences (and the warning will force the developer to correctly fix the problem (by either adding r'...' and fixing the other valid escape chars, or by fixing the invalid escape chars.


---

## Python Code
```python

print('\c \'')
```


Command Line
```shell
$ autopep8 --in-place --recursive --aggressive --aggressive .
```

This outputs
```python

print(r'\c \'')
```


Discussion:

Patch:
diff --git a/autopep8.py b/autopep8.py
index 2a2a067..a7be266 100755
--- a/autopep8.py
+++ b/autopep8.py
@@ -1384,56 +1384,10 @@ class FixPEP8(object):
             next_line[next_line_indent:])
 
     def fix_w605(self, result):
-        (line_index, _, target) = get_index_offset_contents(result,
-                                                            self.source)
-        try:
-            tokens = list(generate_tokens(target))
-        except (SyntaxError, tokenize.TokenError):
-            return
-        for (pos, _msg) in get_w605_position(tokens):
-            if target[pos - 1] == "r":
-                # ignore special case
-                if self.options.verbose:
-                    print("invalid line: line_number={}, line: {}".format(
-                        line_index + 1, target))
-                return
-            self.source[line_index] = '{}r{}'.format(
-                target[:pos], target[pos:])
-
-
-def get_w605_position(tokens):
-    """workaround get pointing out position by W605."""
-    # TODO: When this PR(*) change is released, use pos of pycodestyle
-    # *: https://github.com/PyCQA/pycodestyle/pull/747
-    valid = [
-        '\n', '\\', '\'', '"', 'a', 'b', 'f', 'n', 'r', 't', 'v',
-        '0', '1', '2', '3', '4', '5', '6', '7', 'x',
-
-        # Escape sequences only recognized in string literals
-        'N', 'u', 'U',
-    ]
-
-    for token_type, text, start_pos, _end_pos, _line in tokens:
-        if token_type == tokenize.STRING:
-            quote = text[-3:] if text[-3:] in ('"""', "'''") else text[-1]
-            # Extract string modifiers (e.g. u or r)
-            quote_pos = text.index(quote)
-            prefix = text[:quote_pos].lower()
-            start = quote_pos + len(quote)
-            string = text[start:-len(quote)]
-
-            if 'r' not in prefix:
-                pos = string.find('\\')
-                while pos >= 0:
-                    pos += 1
-                    if string[pos] not in valid:
-                        yield (
-                            # No need to search line, token stores position
-                            start_pos[1],
-                            "W605 invalid escape sequence '\\%s'" %
-                            string[pos],
-                        )
-                    pos = string.find('\\', pos + 1)
+        (line_index, offset, target) = get_index_offset_contents(result,
+                                                                 self.source)
+        self.source[line_index] = '{}\\{}'.format(
+            target[:offset + 1], target[offset + 1:])
 
 
 def get_module_imports_on_top_of_file(source, import_line_index):


Test patch:
diff --git a/test/test_autopep8.py b/test/test_autopep8.py
index a69808f..c7ac13f 100755
--- a/test/test_autopep8.py
+++ b/test/test_autopep8.py
@@ -5037,7 +5037,7 @@ raise ValueError("error")
 
     def test_w605_simple(self):
         line = "escape = '\\.jpg'\n"
-        fixed = "escape = r'\\.jpg'\n"
+        fixed = "escape = '\\\\.jpg'\n"
         with autopep8_context(line, options=['--aggressive']) as result:
             self.assertEqual(fixed, result)
 
@@ -5045,24 +5045,24 @@ raise ValueError("error")
         # ***NOTE***: The --pep8-passes option is required to prevent an infinite loop in
         # the old, failing code. DO NOT REMOVE.
         line = "escape = foo('\\.bar', '\\.kilroy')\n"
-        fixed = "escape = foo(r'\\.bar', r'\\.kilroy')\n"
+        fixed = "escape = foo('\\\\.bar', '\\\\.kilroy')\n"
         with autopep8_context(line, options=['--aggressive', '--pep8-passes', '5']) as result:
             self.assertEqual(fixed, result, "Two tokens get r added")
 
-        line = "escape = foo('\\.bar', r'\\.kilroy')\n"
-        fixed = "escape = foo(r'\\.bar', r'\\.kilroy')\n"
+        line = "escape = foo('\\.bar', '\\\\.kilroy')\n"
+        fixed = "escape = foo('\\\\.bar', '\\\\.kilroy')\n"
         with autopep8_context(line, options=['--aggressive', '--pep8-passes', '5']) as result:
             self.assertEqual(fixed, result, "r not added if already there")
 
         # Test Case to catch bad behavior reported in Issue #449
         line = "escape = foo('\\.bar', '\\.bar')\n"
-        fixed = "escape = foo(r'\\.bar', r'\\.bar')\n"
+        fixed = "escape = foo('\\\\.bar', '\\\\.bar')\n"
         with autopep8_context(line, options=['--aggressive', '--pep8-passes', '5']) as result:
             self.assertEqual(fixed, result)
 
     def test_w605_with_invalid_syntax(self):
         line = "escape = rr'\\.jpg'\n"
-        fixed = "escape = rr'\\.jpg'\n"
+        fixed = "escape = rr'\\\\.jpg'\n"
         with autopep8_context(line, options=['--aggressive']) as result:
             self.assertEqual(fixed, result)