From 1901e63dc45d1f063741bf001067ac52391271be Mon Sep 17 00:00:00 2001 From: Amir Fathi Date: Sun, 12 Jul 2026 03:05:13 -0600 Subject: [PATCH] fix(search): glob '?' matches one character, not two (#1682) --- CHANGELOG.md | 3 +++ src/serena/util/text_utils.py | 2 +- test/serena/test_text_utils.py | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78703962..1ce07eee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ Status of the `main` branch. Changes prior to the next official version change will appear here. * General: + - Fix: in `glob_to_regex` / `search_text(is_glob=True)`, a `?` wildcard matched two characters instead + of one (it emitted `..` rather than `.`); it now matches a single character, consistent with the + `?` semantics documented for `glob_match` and the `test_??.py` example in `search_text`'s docstring. - Add notion of trusted projects via new global configuration setting `trusted_project_path_patterns`. Current effects: - `ls_specific_settings` defined in project configurations will only be applied for trusted projects diff --git a/src/serena/util/text_utils.py b/src/serena/util/text_utils.py index 67110f70..992c2437 100644 --- a/src/serena/util/text_utils.py +++ b/src/serena/util/text_utils.py @@ -124,7 +124,7 @@ def glob_to_regex(glob_pat: str) -> str: if ch == "*": regex_parts.append(".*") elif ch == "?": - regex_parts.append("..") + regex_parts.append(".") elif ch == "\\": i += 1 if i < len(glob_pat): diff --git a/test/serena/test_text_utils.py b/test/serena/test_text_utils.py index 71b0aad6..936779fc 100644 --- a/test/serena/test_text_utils.py +++ b/test/serena/test_text_utils.py @@ -1,9 +1,10 @@ +import re from collections.abc import Callable import pytest from serena.util.file_proxy import FileCollection, FileProxy -from serena.util.text_utils import LineType, MultiFileContentReplacer, search_files, search_text +from serena.util.text_utils import LineType, MultiFileContentReplacer, glob_to_regex, search_files, search_text class TestSearchText: @@ -201,6 +202,20 @@ class TestSearchText: assert len(matches_curly) == 1 assert "{bar}" in matches_curly[0].lines[0].line_content + def test_glob_to_regex_question_matches_single_char(self): + """A glob '?' matches exactly one character, matching glob_match's documented semantics.""" + rx = glob_to_regex("a?c") + assert re.fullmatch(rx, "abc") is not None + assert re.fullmatch(rx, "ac") is None + assert re.fullmatch(rx, "abbc") is None + + def test_search_text_glob_question_single_char(self): + """search_text('c?t', is_glob=True) must match 'cat' (one char), not 'coat' (two chars).""" + content = "value_cat_end\nvalue_coat_end" + matches = search_text("c?t", content=content, is_glob=True, multiline=False) + assert len(matches) == 1 + assert "value_cat_end" in matches[0].lines[0].line_content + def test_search_text_no_matches(self): """Test searching with a pattern that doesn't match anything.""" content = """