@@ -27,19 +27,41 @@ import braces from 'braces';
2727import type { MinimatchOptions } from './types.js' ;
2828
2929/**
30- * Maximum number of patterns that can be generated from brace expansion
31- * This prevents DoS attacks from patterns like {1..1000000}
30+ * Maximum number of patterns that can be generated from brace expansion.
31+ * This prevents DoS attacks from patterns like {1..1000000}.
32+ * Value of 10000 allows reasonable use cases while blocking abuse.
3233 */
3334const MAX_EXPANSION_LENGTH = 10000 ;
3435
3536/**
3637 * Cache for brace expansion results.
3738 * Key is the pattern, value is the expanded array.
38- * Limited to prevent memory issues.
39+ *
40+ * Size of 200 was chosen because:
41+ * - Brace patterns are less common than general globs (~40% of CACHE_SIZE)
42+ * - Each cached array uses more memory than a Minimatch instance
43+ * - 200 entries provide good hit rate for typical brace usage
3944 */
4045const BRACE_CACHE_SIZE = 200 ;
4146const braceCache = new Map < string , string [ ] > ( ) ;
4247
48+ /**
49+ * Add a result to the brace cache with LRU eviction.
50+ * Extracts common cache management logic to avoid duplication.
51+ *
52+ * @param pattern - The pattern key
53+ * @param result - The expanded patterns to cache
54+ */
55+ function addToBraceCache ( pattern : string , result : string [ ] ) : void {
56+ if ( braceCache . size >= BRACE_CACHE_SIZE ) {
57+ const firstKey = braceCache . keys ( ) . next ( ) . value ;
58+ if ( firstKey !== undefined ) {
59+ braceCache . delete ( firstKey ) ;
60+ }
61+ }
62+ braceCache . set ( pattern , result ) ;
63+ }
64+
4365/**
4466 * Simple brace pattern regex: prefix{a,b,c}suffix
4567 * Matches patterns with a single brace group containing comma-separated values
@@ -101,14 +123,7 @@ export function braceExpand(
101123 // Try fast path for simple brace patterns
102124 const fastResult = trySimpleBraceExpand ( pattern ) ;
103125 if ( fastResult !== null ) {
104- // Cache and return
105- if ( braceCache . size >= BRACE_CACHE_SIZE ) {
106- const firstKey = braceCache . keys ( ) . next ( ) . value ;
107- if ( firstKey !== undefined ) {
108- braceCache . delete ( firstKey ) ;
109- }
110- }
111- braceCache . set ( pattern , fastResult ) ;
126+ addToBraceCache ( pattern , fastResult ) ;
112127 return [ ...fastResult ] ;
113128 }
114129
@@ -123,65 +138,19 @@ export function braceExpand(
123138
124139 // Check if expansion is too large (additional safety)
125140 if ( expanded . length > MAX_EXPANSION_LENGTH ) {
126- // Return original pattern if expansion is too large
127- console . warn (
128- `Brace expansion for "${ pattern } " produced ${ expanded . length } patterns, limiting to original pattern`
129- ) ;
141+ // Return original pattern silently if expansion is too large
130142 return [ pattern ] ;
131143 }
132144
133145 // Remove duplicates using Set (faster than manual loop for larger arrays)
134146 const unique = [ ...new Set ( expanded ) ] ;
135147 const result = unique . length > 0 ? unique : [ pattern ] ;
136148
137- // Cache the result
138- if ( braceCache . size >= BRACE_CACHE_SIZE ) {
139- const firstKey = braceCache . keys ( ) . next ( ) . value ;
140- if ( firstKey !== undefined ) {
141- braceCache . delete ( firstKey ) ;
142- }
143- }
144- braceCache . set ( pattern , result ) ;
145-
149+ addToBraceCache ( pattern , result ) ;
146150 return [ ...result ] ;
147- } catch ( e ) {
151+ } catch {
148152 // If expansion fails for any reason, return original pattern
149153 // This matches minimatch's behavior of being lenient with invalid patterns
150154 return [ pattern ] ;
151155 }
152156}
153-
154- /**
155- * Check if a pattern contains brace expansion syntax
156- */
157- export function hasBraces ( pattern : string ) : boolean {
158- // Simple check for balanced braces with content
159- let depth = 0 ;
160- let hasContent = false ;
161-
162- for ( let i = 0 ; i < pattern . length ; i ++ ) {
163- const char = pattern [ i ] ;
164- const prevChar = i > 0 ? pattern [ i - 1 ] : '' ;
165-
166- // Skip escaped braces
167- if ( prevChar === '\\' ) {
168- continue ;
169- }
170-
171- if ( char === '{' ) {
172- depth ++ ;
173- } else if ( char === '}' ) {
174- if ( depth > 0 ) {
175- depth -- ;
176- if ( depth === 0 && hasContent ) {
177- return true ;
178- }
179- }
180- } else if ( depth > 0 && ( char === ',' || char === '.' ) ) {
181- // Has content (comma for list, dots for range)
182- hasContent = true ;
183- }
184- }
185-
186- return false ;
187- }
0 commit comments