From 739f4f91ef0ef8d9fb1651c6465cacfe9950d644 Mon Sep 17 00:00:00 2001 From: Shubham Jaiswal Date: Sun, 1 Jun 2025 11:31:30 +0530 Subject: [PATCH] fix for issue #815 --- rope/refactor/occurrences.py | 30 +++++++++++++++++++++++++++++- ropetest/refactor/inlinetest.py | 16 ++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/rope/refactor/occurrences.py b/rope/refactor/occurrences.py index b136cc5c..cf045d51 100644 --- a/rope/refactor/occurrences.py +++ b/rope/refactor/occurrences.py @@ -339,7 +339,21 @@ def _re_search(self, source: str) -> Iterator[int]: yield match.start("fstring") + offset def _search_in_f_string(self, f_string: str) -> Iterator[int]: - tree = ast.parse(f_string) + # Handle f-strings more robustly, especially for Python 3.12+ nested quotes + try: + # First, try to parse the f-string content as an expression + tree = ast.parse(f_string, mode='eval') + except SyntaxError: + # If that fails, try as a module (statement-level parsing) + try: + tree = ast.parse(f_string, mode='exec') + except SyntaxError: + # If AST parsing fails completely, fall back to textual search + # This handles malformed f-strings that can't be parsed + yield from self._fallback_search_in_string(f_string) + return + + # Walk the AST to find name occurrences for node in ast.walk(tree): if isinstance(node, ast.Name) and node.id == self.name: yield node.col_offset @@ -347,6 +361,20 @@ def _search_in_f_string(self, f_string: str) -> Iterator[int]: assert node.end_col_offset is not None yield node.end_col_offset - len(self.name) + def _fallback_search_in_string(self, text: str) -> Iterator[int]: + """Fallback textual search when AST parsing fails""" + current = 0 + while True: + try: + found = text.index(self.name, current) + current = found + len(self.name) + # Check if it's a valid identifier boundary + if (found == 0 or not self._is_id_char(text[found - 1])) and \ + (current == len(text) or not self._is_id_char(text[current])): + yield found + except ValueError: + break + def _normal_search(self, source: str) -> Iterator[int]: current = 0 while True: diff --git a/ropetest/refactor/inlinetest.py b/ropetest/refactor/inlinetest.py index 585214b4..db655e07 100644 --- a/ropetest/refactor/inlinetest.py +++ b/ropetest/refactor/inlinetest.py @@ -1521,3 +1521,19 @@ def a_func(arg1, *, arg2=2): """) with self.assertRaises(rope.base.exceptions.RefactoringError): refactored = self._inline(code, code.index("a_func") + 1) + + @testutils.only_for_versions_higher("3.12") + def test_inlining_with_fstring_nested_quotes(self): + code = dedent('''\ + s = "hello" + print(f'Value: {s}') + another_var = s + f'{''}' + ''') + expected = dedent('''\ + print(f'Value: {"hello"}') + another_var = "hello" + f'{''}' + ''') + refactored = self._inline(code, code.index("s") + 1) + self.assertEqual(expected, refactored)