From e19b0b6d34dca5d91b56aa57da1f1b8573f9564b Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 13 Jun 2025 11:54:56 +0200 Subject: [PATCH 1/2] Fix glob pattern escaping --- src/serena/text_utils.py | 16 +++------------- test/serena/test_text_utils.py | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/serena/text_utils.py b/src/serena/text_utils.py index 338b295..380eb91 100644 --- a/src/serena/text_utils.py +++ b/src/serena/text_utils.py @@ -153,19 +153,9 @@ def search_text( # Convert pattern to a compiled regex if it's a string if is_glob and isinstance(pattern, str): - # Convert glob pattern to regex - # Escape all regex special characters except * and ? - regex_special_chars = r"\^$.|+()[{" - escaped_pattern = "" - for char in pattern: - if char in regex_special_chars: - escaped_pattern += "\\" + char - elif char == "*": - escaped_pattern += ".*" - elif char == "?": - escaped_pattern += "." - else: - escaped_pattern += char + # Convert glob pattern to regex. Use re.escape to handle special + # characters and replace glob wildcards afterwards. + escaped_pattern = "".join(".*" if ch == "*" else "." if ch == "?" else re.escape(ch) for ch in pattern) # For glob patterns, don't anchor with ^ and $ to allow partial line matches compiled_pattern = re.compile(escaped_pattern) elif isinstance(pattern, str): diff --git a/test/serena/test_text_utils.py b/test/serena/test_text_utils.py index 1fa6929..860ce6a 100644 --- a/test/serena/test_text_utils.py +++ b/test/serena/test_text_utils.py @@ -182,6 +182,24 @@ class TestSearchText: assert any("isinstance(item, dict)" in line for line in instance_matches) assert any("isinstance(item, list)" in line for line in instance_matches) + def test_search_text_glob_with_special_chars(self): + """Glob patterns containing regex special characters should match literally.""" + content = """ + def func_square(): + print("value[42]") + + def func_curly(): + print("value{bar}") + """ + + matches_square = search_text(r"*\[42\]*", content=content, is_glob=True) + assert len(matches_square) == 1 + assert "[42]" in matches_square[0].lines[0].line_content + + matches_curly = search_text("*{bar}*", content=content, is_glob=True) + assert len(matches_curly) == 1 + assert "{bar}" in matches_curly[0].lines[0].line_content + def test_search_text_no_matches(self): """Test searching with a pattern that doesn't match anything.""" content = """ From 34cef92c6cd681fd0294788ee907c2f431e5c894 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Fri, 13 Jun 2025 12:51:53 +0200 Subject: [PATCH 2/2] Fix glob escaping for search_text --- src/serena/text_utils.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/serena/text_utils.py b/src/serena/text_utils.py index 380eb91..75cc7dd 100644 --- a/src/serena/text_utils.py +++ b/src/serena/text_utils.py @@ -153,9 +153,28 @@ def search_text( # Convert pattern to a compiled regex if it's a string if is_glob and isinstance(pattern, str): - # Convert glob pattern to regex. Use re.escape to handle special - # characters and replace glob wildcards afterwards. - escaped_pattern = "".join(".*" if ch == "*" else "." if ch == "?" else re.escape(ch) for ch in pattern) + # Convert glob pattern with optional backslash escaping to regex + def glob_to_regex(glob_pat: str) -> str: + regex_parts: list[str] = [] + i = 0 + while i < len(glob_pat): + ch = glob_pat[i] + if ch == "*": + regex_parts.append(".*") + elif ch == "?": + regex_parts.append(".") + elif ch == "\\": + i += 1 + if i < len(glob_pat): + regex_parts.append(re.escape(glob_pat[i])) + else: + regex_parts.append("\\") + else: + regex_parts.append(re.escape(ch)) + i += 1 + return "".join(regex_parts) + + escaped_pattern = glob_to_regex(pattern) # For glob patterns, don't anchor with ^ and $ to allow partial line matches compiled_pattern = re.compile(escaped_pattern) elif isinstance(pattern, str):