@@ -3034,10 +3034,6 @@ namespace ts {
30343034 return filter ( map ( values , v => convertJsonOption ( option . element , v , basePath , errors ) ) , v => ! ! v ) ;
30353035 }
30363036
3037- function trimString ( s : string ) {
3038- return typeof s . trim === "function" ? s . trim ( ) : s . replace ( / ^ [ \s ] + | [ \s ] + $ / g, "" ) ;
3039- }
3040-
30413037 /**
30423038 * Tests for a path that ends in a recursive directory wildcard.
30433039 * Matches **, \**, **\, and \**\, but not a**b.
@@ -3051,36 +3047,6 @@ namespace ts {
30513047 */
30523048 const invalidTrailingRecursionPattern = / ( ^ | \/ ) \* \* \/ ? $ / ;
30533049
3054- /**
3055- * Tests for a path where .. appears after a recursive directory wildcard.
3056- * Matches **\..\*, **\a\..\*, and **\.., but not ..\**\*
3057- *
3058- * NOTE: used \ in place of / above to avoid issues with multiline comments.
3059- *
3060- * Breakdown:
3061- * (^|\/) # matches either the beginning of the string or a directory separator.
3062- * \*\*\/ # matches a recursive directory wildcard "**" followed by a directory separator.
3063- * (.*\/)? # optionally matches any number of characters followed by a directory separator.
3064- * \.\. # matches a parent directory path component ".."
3065- * ($|\/) # matches either the end of the string or a directory separator.
3066- */
3067- const invalidDotDotAfterRecursiveWildcardPattern = / ( ^ | \/ ) \* \* \/ ( .* \/ ) ? \. \. ( $ | \/ ) / ;
3068-
3069- /**
3070- * Tests for a path containing a wildcard character in a directory component of the path.
3071- * Matches \*\, \?\, and \a*b\, but not \a\ or \a\*.
3072- *
3073- * NOTE: used \ in place of / above to avoid issues with multiline comments.
3074- *
3075- * Breakdown:
3076- * \/ # matches a directory separator.
3077- * [^/]*? # matches any number of characters excluding directory separators (non-greedy).
3078- * [*?] # matches either a wildcard character (* or ?)
3079- * [^/]* # matches any number of characters excluding directory separators (greedy).
3080- * \/ # matches a directory separator.
3081- */
3082- const watchRecursivePattern = / \/ [ ^ / ] * ?[ * ? ] [ ^ / ] * \/ / ;
3083-
30843050 /**
30853051 * Matches the portion of a wildcard path that does not contain wildcards.
30863052 * Matches \a of \a\*, or \a\b\c of \a\b\c\?\d.
@@ -3217,6 +3183,20 @@ namespace ts {
32173183 return matchesExcludeWorker ( pathToCheck , validatedExcludeSpecs , useCaseSensitiveFileNames , currentDirectory , basePath ) ;
32183184 }
32193185
3186+ function invalidDotDotAfterRecursiveWildcard ( s : string ) {
3187+ // We used to use the regex /(^|\/)\*\*\/(.*\/)?\.\.($|\/)/ to check for this case, but
3188+ // in v8, that has polynomial performance because the recursive wildcard match - **/ -
3189+ // can be matched in many arbitrary positions when multiple are present, resulting
3190+ // in bad backtracking (and we don't care which is matched - just that some /.. segment
3191+ // comes after some **/ segment).
3192+ const wildcardIndex = startsWith ( s , "**/" ) ? 0 : s . indexOf ( "/**/" ) ;
3193+ if ( wildcardIndex === - 1 ) {
3194+ return false ;
3195+ }
3196+ const lastDotIndex = endsWith ( s , "/.." ) ? s . length : s . lastIndexOf ( "/../" ) ;
3197+ return lastDotIndex > wildcardIndex ;
3198+ }
3199+
32203200 /* @internal */
32213201 export function matchesExclude (
32223202 pathToCheck : string ,
@@ -3226,7 +3206,7 @@ namespace ts {
32263206 ) {
32273207 return matchesExcludeWorker (
32283208 pathToCheck ,
3229- filter ( excludeSpecs , spec => ! invalidDotDotAfterRecursiveWildcardPattern . test ( spec ) ) ,
3209+ filter ( excludeSpecs , spec => ! invalidDotDotAfterRecursiveWildcard ( spec ) ) ,
32303210 useCaseSensitiveFileNames ,
32313211 currentDirectory
32323212 ) ;
@@ -3268,7 +3248,7 @@ namespace ts {
32683248 if ( disallowTrailingRecursion && invalidTrailingRecursionPattern . test ( spec ) ) {
32693249 return [ Diagnostics . File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0 , spec ] ;
32703250 }
3271- else if ( invalidDotDotAfterRecursiveWildcardPattern . test ( spec ) ) {
3251+ else if ( invalidDotDotAfterRecursiveWildcard ( spec ) ) {
32723252 return [ Diagnostics . File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0 , spec ] ;
32733253 }
32743254 }
@@ -3331,9 +3311,18 @@ namespace ts {
33313311 function getWildcardDirectoryFromSpec ( spec : string , useCaseSensitiveFileNames : boolean ) : { key : string , flags : WatchDirectoryFlags } | undefined {
33323312 const match = wildcardDirectoryPattern . exec ( spec ) ;
33333313 if ( match ) {
3314+ // We check this with a few `indexOf` calls because 3 `indexOf`/`lastIndexOf` calls is
3315+ // less algorithmically complex (roughly O(3n) worst-case) than the regex we used to use,
3316+ // \/[^/]*?[*?][^/]*\/ which was polynominal in v8, since arbitrary sequences of wildcard
3317+ // characters could match any of the central patterns, resulting in bad backtracking.
3318+ const questionWildcardIndex = spec . indexOf ( "?" ) ;
3319+ const starWildcardIndex = spec . indexOf ( "*" ) ;
3320+ const lastDirectorySeperatorIndex = spec . lastIndexOf ( directorySeparator ) ;
33343321 return {
33353322 key : useCaseSensitiveFileNames ? match [ 0 ] : toFileNameLowerCase ( match [ 0 ] ) ,
3336- flags : watchRecursivePattern . test ( spec ) ? WatchDirectoryFlags . Recursive : WatchDirectoryFlags . None
3323+ flags : ( questionWildcardIndex !== - 1 && questionWildcardIndex < lastDirectorySeperatorIndex )
3324+ || ( starWildcardIndex !== - 1 && starWildcardIndex < lastDirectorySeperatorIndex )
3325+ ? WatchDirectoryFlags . Recursive : WatchDirectoryFlags . None
33373326 } ;
33383327 }
33393328 if ( isImplicitGlob ( spec ) ) {
0 commit comments