From 5c78e53fa7ee0293972ba0f252c2c287cc27ae36 Mon Sep 17 00:00:00 2001 From: sandratsy <> Date: Wed, 24 Jan 2024 00:08:57 +0800 Subject: [PATCH 1/4] Check ast.Attributes (e.g. ) when finding occurrences in fstrings --- rope/refactor/occurrences.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rope/refactor/occurrences.py b/rope/refactor/occurrences.py index 89ffdcd91..fb9db9921 100644 --- a/rope/refactor/occurrences.py +++ b/rope/refactor/occurrences.py @@ -335,14 +335,16 @@ def _re_search(self, source): 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): 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: + yield node.end_col_offset - len(self.name) def _normal_search(self, source): current = 0 From e25e976a97655fd67a0d2e82e31b2d2f51ff17e1 Mon Sep 17 00:00:00 2001 From: sandratsy <> Date: Wed, 24 Jan 2024 00:32:29 +0800 Subject: [PATCH 2/4] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05b30b20d..092d5fbaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # **Upcoming release** -- ... +- Check for ast.Attributes when finding occurrences in fstrings (@sandratsy) # Release 1.12.0 From 85fd3036fea2fbb0beed0c0a37f0315766688f22 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Tue, 5 Mar 2024 21:40:24 +1100 Subject: [PATCH 3/4] add type hints --- rope/refactor/occurrences.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/rope/refactor/occurrences.py b/rope/refactor/occurrences.py index fb9db9921..546c83a3f 100644 --- a/rope/refactor/occurrences.py +++ b/rope/refactor/occurrences.py @@ -38,6 +38,7 @@ import contextlib import re +from typing import Iterator from rope.base import ( ast, @@ -320,7 +321,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: @@ -329,7 +330,7 @@ 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") @@ -338,15 +339,16 @@ def _re_search(self, source): 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.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: From 3d2cc6dff3b46570addfa3b86f21c9c87ffad46c Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Tue, 5 Mar 2024 21:40:38 +1100 Subject: [PATCH 4/4] add tests for occurrences in f-string --- ropetest/refactor/renametest.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/ropetest/refactor/renametest.py b/ropetest/refactor/renametest.py index baa1e7579..43b7335e7 100644 --- a/ropetest/refactor/renametest.py +++ b/ropetest/refactor/renametest.py @@ -328,6 +328,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"