From 8296c2b387945541cd24b3b3ff19845c48fdb8bf Mon Sep 17 00:00:00 2001 From: sergio-sisternes-epam Date: Wed, 4 Mar 2026 15:39:15 +0000 Subject: [PATCH] fix: handle multiple brace groups in applyTo glob patterns _expand_glob_pattern() used str.format() which misinterpreted the second brace group in patterns like **/*.{test,spec}.{ts,js,mts,mjs} as a Python format placeholder, raising KeyError. Replace with recursive brace expansion that correctly handles any number of brace groups by expanding one group at a time. Fixes #153 --- src/apm_cli/compilation/context_optimizer.py | 16 +++++++--- .../compilation/test_context_optimizer.py | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/apm_cli/compilation/context_optimizer.py b/src/apm_cli/compilation/context_optimizer.py index 5fd67c15..66a9aea2 100644 --- a/src/apm_cli/compilation/context_optimizer.py +++ b/src/apm_cli/compilation/context_optimizer.py @@ -738,22 +738,28 @@ def _extract_intended_directory_from_pattern(self, pattern: str) -> Optional[Pat return None def _expand_glob_pattern(self, pattern: str) -> List[str]: - """Expand glob pattern with brace expansion. + """Expand glob pattern with brace expansion, supporting multiple brace groups. Args: - pattern (str): Pattern like '**/*.{css,scss}' + pattern (str): Pattern like '**/*.{css,scss}' or '**/*.{test,spec}.{ts,js}' Returns: List[str]: Expanded patterns like ['**/*.css', '**/*.scss'] + or ['**/*.test.ts', '**/*.test.js', '**/*.spec.ts', '**/*.spec.js'] """ import re # Handle brace expansion like {css,scss} brace_match = re.search(r'\{([^}]+)\}', pattern) if brace_match: - extensions = brace_match.group(1).split(',') - base_pattern = pattern[:brace_match.start()] + '{}' + pattern[brace_match.end():] - return [base_pattern.format(ext) for ext in extensions] + alternatives = brace_match.group(1).split(',') + prefix = pattern[:brace_match.start()] + suffix = pattern[brace_match.end():] + # Recursively expand remaining brace groups in each result + expanded = [] + for alt in alternatives: + expanded.extend(self._expand_glob_pattern(prefix + alt + suffix)) + return expanded return [pattern] diff --git a/tests/unit/compilation/test_context_optimizer.py b/tests/unit/compilation/test_context_optimizer.py index 62d09e93..9ec96799 100644 --- a/tests/unit/compilation/test_context_optimizer.py +++ b/tests/unit/compilation/test_context_optimizer.py @@ -677,5 +677,37 @@ def test_default_exclusions_still_work(self): assert base_path / "custom_exclude" not in cached_dirs # Custom exclusion +class TestExpandGlobPattern: + """Test _expand_glob_pattern brace expansion.""" + + def test_single_brace_group(self): + """Test expansion of a single brace group.""" + optimizer = ContextOptimizer(base_dir="/tmp") + result = optimizer._expand_glob_pattern("**/*.{css,scss}") + assert sorted(result) == sorted(["**/*.css", "**/*.scss"]) + + def test_multiple_brace_groups(self): + """Test expansion of multiple brace groups (Fixes #153).""" + optimizer = ContextOptimizer(base_dir="/tmp") + result = optimizer._expand_glob_pattern("**/*.{test,spec}.{ts,js,mts,mjs}") + expected = [ + "**/*.test.ts", "**/*.test.js", "**/*.test.mts", "**/*.test.mjs", + "**/*.spec.ts", "**/*.spec.js", "**/*.spec.mts", "**/*.spec.mjs", + ] + assert sorted(result) == sorted(expected) + + def test_no_brace_group(self): + """Test pattern without braces is returned as-is.""" + optimizer = ContextOptimizer(base_dir="/tmp") + result = optimizer._expand_glob_pattern("**/*.py") + assert result == ["**/*.py"] + + def test_single_item_brace_group(self): + """Test brace group with a single item.""" + optimizer = ContextOptimizer(base_dir="/tmp") + result = optimizer._expand_glob_pattern("**/*.{ts}") + assert result == ["**/*.ts"] + + if __name__ == "__main__": pytest.main([__file__]) \ No newline at end of file