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