Skip to content

Commit

Permalink
Merge pull request #420 from python-rope/lieryan-inlining-into-f-string
Browse files Browse the repository at this point in the history
Fix inlining into f-string containing quote characters
  • Loading branch information
lieryan committed Sep 27, 2021
2 parents 2868b68 + 823ec40 commit c051d60
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 10 deletions.
9 changes: 9 additions & 0 deletions .all-contributorsrc
Expand Up @@ -481,6 +481,15 @@
"contributions": [
"bug"
]
},
{
"login": "mlao-pdx",
"name": "MLAO",
"avatar_url": "https://avatars.githubusercontent.com/u/21014310?v=4",
"profile": "https://github.com/mlao-pdx",
"contributions": [
"bug"
]
}
],
"projectName": "rope",
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,8 @@

## Bug fixes
- #391, #396 Extract method similar no longer replace the left-hand side of assignment
- #303 Fix inlining into f-string containing quote characters




Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Expand Up @@ -72,6 +72,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://sourcery.ai/"><img src="https://avatars.githubusercontent.com/u/1440886?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brendan Maginnis</b></sub></a><br /><a href="https://github.com/python-rope/rope/commits?author=brendanator" title="Code">💻</a></td>
<td align="center"><a href="https://solovyov.net/"><img src="https://avatars.githubusercontent.com/u/6553?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexander Solovyov</b></sub></a><br /><a href="https://github.com/python-rope/rope/commits?author=piranha" title="Code">💻</a></td>
<td align="center"><a href="https://mmfftt.blogspot.com/"><img src="https://avatars.githubusercontent.com/u/1430953?v=4?s=100" width="100px;" alt=""/><br /><sub><b>MATSUI Tetsushi</b></sub></a><br /><a href="https://github.com/python-rope/rope/issues?q=author%3Amft" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/mlao-pdx"><img src="https://avatars.githubusercontent.com/u/21014310?v=4?s=100" width="100px;" alt=""/><br /><sub><b>MLAO</b></sub></a><br /><a href="https://github.com/python-rope/rope/issues?q=author%3Amlao-pdx" title="Bug reports">🐛</a></td>
</tr>
</table>

Expand Down
30 changes: 25 additions & 5 deletions rope/base/codeanalyze.py
Expand Up @@ -351,11 +351,23 @@ def count_line_indents(line):
return 0


def get_string_pattern_with_prefix(prefix):
longstr = r'%s"""(\\.|"(?!"")|\\\n|[^"\\])*"""' % prefix
shortstr = r'%s"(\\.|\\\n|[^"\\\n])*"' % prefix
return "|".join(
[longstr, longstr.replace('"', "'"), shortstr, shortstr.replace('"', "'")]
def get_string_pattern_with_prefix(prefix, prefix_group_name=None):
longstr = r'"""(\\.|"(?!"")|\\\n|[^"\\])*"""'
shortstr = r'"(\\.|\\\n|[^"\\\n])*"'
if prefix_group_name is not None:
pattern = "(?P<%s>%%s)(%%s)" % prefix_group_name
else:
pattern = "%s(%s)"
return pattern % (
prefix,
"|".join(
[
longstr,
longstr.replace('"', "'"),
shortstr,
shortstr.replace('"', "'"),
]
),
)


Expand All @@ -369,5 +381,13 @@ def get_formatted_string_pattern():
return get_string_pattern_with_prefix(prefix)


def get_any_string_pattern():
prefix = r"[bBfFrRuU]{,4}"
return get_string_pattern_with_prefix(
prefix,
prefix_group_name="prefix",
)


def get_comment_pattern():
return r"#[^\n]*"
19 changes: 15 additions & 4 deletions rope/base/simplify.py
Expand Up @@ -22,12 +22,15 @@ def real_code(source):
only in offsets.
"""
collector = codeanalyze.ChangeCollector(source)
for start, end in ignored_regions(source):
for start, end, matchgroups in ignored_regions(source):
if source[start] == "#":
replacement = " " * (end - start)
elif "f" in matchgroups.get("prefix", "").lower():
replacement = None
else:
replacement = '"%s"' % (" " * (end - start - 2))
collector.add_change(start, end, replacement)
if replacement is not None:
collector.add_change(start, end, replacement)
source = collector.get_changed() or source
collector = codeanalyze.ChangeCollector(source)
parens = 0
Expand All @@ -47,10 +50,18 @@ def real_code(source):
@utils.cached(7)
def ignored_regions(source):
"""Return ignored regions like strings and comments in `source`"""
return [(match.start(), match.end()) for match in _str.finditer(source)]
return [
(match.start(), match.end(), match.groupdict())
for match in _str.finditer(source)
]


_str = re.compile(
"%s|%s" % (codeanalyze.get_comment_pattern(), codeanalyze.get_string_pattern())
"|".join(
[
codeanalyze.get_comment_pattern(),
codeanalyze.get_any_string_pattern(),
]
)
)
_parens = re.compile(r"[\({\[\]}\)\n]")
42 changes: 42 additions & 0 deletions ropetest/refactor/inlinetest.py
@@ -1,3 +1,5 @@
from textwrap import dedent

try:
import unittest2 as unittest
except ImportError:
Expand Down Expand Up @@ -669,3 +671,43 @@ def test_inlining_does_change_string_constants_if_docs_is_set(self):
code, code.rindex("var"), remove=False, only_current=True, docs=True
)
self.assertEqual(expected, refactored)

@testutils.only_for_versions_higher("3.6")
def test_inlining_into_format_string(self):
code = dedent(
"""\
var = 123
print(f"{var}")
"""
)
expected = dedent(
"""\
print(f"{123}")
"""
)

refactored = self._inline(code, code.rindex("var"))

self.assertEqual(expected, refactored)

@testutils.only_for_versions_higher("3.6")
def test_inlining_into_format_string_containing_quotes(self):
code = dedent(
'''\
var = 123
print(f" '{var}' ")
print(f""" "{var}" """)
print(f' "{var}" ')
'''
)
expected = dedent(
'''\
print(f" '{123}' ")
print(f""" "{123}" """)
print(f' "{123}" ')
'''
)

refactored = self._inline(code, code.rindex("var"))

self.assertEqual(expected, refactored)
13 changes: 12 additions & 1 deletion ropetest/simplifytest.py
Expand Up @@ -7,7 +7,6 @@


class SimplifyTest(unittest.TestCase):

def test_trivial_case(self):
self.assertEqual("", simplify.real_code(""))

Expand Down Expand Up @@ -54,3 +53,15 @@ def test_replacing_tabs(self):
def test_replacing_semicolons(self):
code = "a = 1;b = 2\n"
self.assertEqual("a = 1\nb = 2\n", simplify.real_code(code))

def test_simplifying_f_string(self):
code = 's = f"..{hello}.."\n'
self.assertEqual('s = f"..{hello}.."\n', simplify.real_code(code))

def test_simplifying_f_string_containing_quotes(self):
code = """s = f"..'{hello}'.."\n"""
self.assertEqual("""s = f"..'{hello}'.."\n""", simplify.real_code(code))

def test_simplifying_uppercase_f_string_containing_quotes(self):
code = """s = Fr"..'{hello}'.."\n"""
self.assertEqual("""s = Fr"..'{hello}'.."\n""", simplify.real_code(code))

0 comments on commit c051d60

Please sign in to comment.