Skip to content

Commit

Permalink
Merge pull request #751 from sandratsy/master
Browse files Browse the repository at this point in the history
Check for ast.Attributes when finding occurrences in f-strings
  • Loading branch information
lieryan authored Mar 5, 2024
2 parents 8b3478e + 60d0e02 commit faf4456
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# **Upcoming release**

- Check for ast.Attributes when finding occurrences in fstrings (@sandratsy)
- #777, #698 add validation to refuse Rename refactoring to a python keyword
- #730 Match on module aliases for autoimport suggestions
- #755 Remove dependency on `build` package being installed while running tests
Expand Down
18 changes: 11 additions & 7 deletions rope/refactor/occurrences.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

import contextlib
import re
from typing import Iterator

from rope.base import (
ast,
Expand Down Expand Up @@ -319,7 +320,7 @@ def __init__(self, name, docs=False):
)
self.pattern = self._get_occurrence_pattern(self.name)

def find_offsets(self, source):
def find_offsets(self, source: str) -> Iterator[int]:
if not self._fast_file_query(source):
return
if self.docs:
Expand All @@ -328,22 +329,25 @@ def find_offsets(self, source):
searcher = self._re_search
yield from searcher(source)

def _re_search(self, source):
def _re_search(self, source: str) -> Iterator[int]:
for match in self.pattern.finditer(source):
if match.groupdict()["occurrence"]:
yield match.start("occurrence")
elif match.groupdict()["fstring"]:
f_string = match.groupdict()["fstring"]
for occurrence_node in self._search_in_f_string(f_string):
yield match.start("fstring") + occurrence_node.col_offset
for offset in self._search_in_f_string(f_string):
yield match.start("fstring") + offset

def _search_in_f_string(self, f_string):
def _search_in_f_string(self, f_string: str) -> Iterator[int]:
tree = ast.parse(f_string)
for node in ast.walk(tree):
if isinstance(node, ast.Name) and node.id == self.name:
yield node
yield node.col_offset
elif isinstance(node, ast.Attribute) and node.attr == self.name:
assert node.end_col_offset is not None
yield node.end_col_offset - len(self.name)

def _normal_search(self, source):
def _normal_search(self, source: str) -> Iterator[int]:
current = 0
while True:
try:
Expand Down
22 changes: 22 additions & 0 deletions ropetest/refactor/renametest.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,28 @@ def test_renaming_occurrence_in_nested_f_string(self):
refactored = self._local_rename(code, 2, "new_var")
self.assertEqual(expected, refactored)

def test_renaming_attribute_occurrences_in_f_string(self):
code = dedent("""\
class MyClass:
def __init__(self):
self.abc = 123
def func(obj):
print(f'{obj.abc}')
return obj.abc
""")
expected = dedent("""\
class MyClass:
def __init__(self):
self.new_var = 123
def func(obj):
print(f'{obj.new_var}')
return obj.new_var
""")
refactored = self._local_rename(code, code.index('abc'), "new_var")
self.assertEqual(expected, refactored)

def test_not_renaming_string_contents_in_f_string(self):
refactored = self._local_rename(
"a_var = 20\na_string=f'{\"a_var\"}'\n", 2, "new_var"
Expand Down

0 comments on commit faf4456

Please sign in to comment.