@@ -13,6 +13,7 @@ export class ReplacePattern {
1313 private _replacePattern : string ;
1414 private _hasParameters : boolean = false ;
1515 private _regExp : RegExp ;
16+ private _caseOpsRegExp : RegExp ;
1617
1718 constructor ( replaceString : string , searchPatternInfo : IPatternInfo )
1819 constructor ( replaceString : string , parseParameters : boolean , regEx : RegExp )
@@ -37,6 +38,8 @@ export class ReplacePattern {
3738 if ( this . _regExp . global ) {
3839 this . _regExp = strings . createRegExp ( this . _regExp . source , true , { matchCase : ! this . _regExp . ignoreCase , wholeWord : false , multiline : this . _regExp . multiline , global : false } ) ;
3940 }
41+
42+ this . _caseOpsRegExp = new RegExp ( / ( [ ^ \\ ] * ?) ( (?: \\ [ u U l L ] ) + ?| ) ( \$ [ 0 - 9 ] + ) ( .* ?) / g) ;
4043 }
4144
4245 get hasParameters ( ) : boolean {
@@ -60,10 +63,10 @@ export class ReplacePattern {
6063 const match = this . _regExp . exec ( text ) ;
6164 if ( match ) {
6265 if ( this . hasParameters ) {
66+ const replaceString = this . replaceWithCaseOperations ( text , this . _regExp , this . buildReplaceString ( match , preserveCase ) ) ;
6367 if ( match [ 0 ] === text ) {
64- return text . replace ( this . _regExp , this . buildReplaceString ( match , preserveCase ) ) ;
68+ return replaceString ;
6569 }
66- const replaceString = text . replace ( this . _regExp , this . buildReplaceString ( match , preserveCase ) ) ;
6770 return replaceString . substr ( match . index , match [ 0 ] . length - ( text . length - replaceString . length ) ) ;
6871 }
6972 return this . buildReplaceString ( match , preserveCase ) ;
@@ -72,6 +75,84 @@ export class ReplacePattern {
7275 return null ;
7376 }
7477
78+ /**
79+ * replaceWithCaseOperations applies case operations to relevant replacement strings and applies
80+ * the affected $N arguments. It then passes unaffected $N arguments through to string.replace().
81+ *
82+ * \u => upper-cases one character in a match.
83+ * \U => upper-cases ALL remaining characters in a match.
84+ * \l => lower-cases one character in a match.
85+ * \L => lower-cases ALL remaining characters in a match.
86+ */
87+ private replaceWithCaseOperations ( text : string , regex : RegExp , replaceString : string ) : string {
88+ // Short-circuit the common path.
89+ if ( ! / \\ [ u U l L ] / . test ( replaceString ) ) {
90+ return text . replace ( regex , replaceString ) ;
91+ }
92+ // Store the values of the search parameters.
93+ const firstMatch = regex . exec ( text ) ;
94+ if ( firstMatch === null ) {
95+ return text . replace ( regex , replaceString ) ;
96+ }
97+
98+ let patMatch : RegExpExecArray | null ;
99+ let newReplaceString = '' ;
100+ let lastIndex = 0 ;
101+ let lastMatch = '' ;
102+ // For each annotated $N, perform text processing on the parameters and perform the substitution.
103+ while ( ( patMatch = this . _caseOpsRegExp . exec ( replaceString ) ) !== null ) {
104+ lastIndex = patMatch . index ;
105+ const fullMatch = patMatch [ 0 ] ;
106+ lastMatch = fullMatch ;
107+ let caseOps = patMatch [ 2 ] ; // \u, \l\u, etc.
108+ const money = patMatch [ 3 ] ; // $1, $2, etc.
109+
110+ if ( ! caseOps ) {
111+ newReplaceString += fullMatch ;
112+ continue ;
113+ }
114+ const replacement = firstMatch [ parseInt ( money . slice ( 1 ) ) ] ;
115+ if ( ! replacement ) {
116+ newReplaceString += fullMatch ;
117+ continue ;
118+ }
119+ const replacementLen = replacement . length ;
120+
121+ newReplaceString += patMatch [ 1 ] ; // prefix
122+ caseOps = caseOps . replace ( / \\ / g, '' ) ;
123+ let i = 0 ;
124+ for ( ; i < caseOps . length ; i ++ ) {
125+ switch ( caseOps [ i ] ) {
126+ case 'U' :
127+ newReplaceString += replacement . slice ( i ) . toUpperCase ( ) ;
128+ i = replacementLen ;
129+ break ;
130+ case 'u' :
131+ newReplaceString += replacement [ i ] . toUpperCase ( ) ;
132+ break ;
133+ case 'L' :
134+ newReplaceString += replacement . slice ( i ) . toLowerCase ( ) ;
135+ i = replacementLen ;
136+ break ;
137+ case 'l' :
138+ newReplaceString += replacement [ i ] . toLowerCase ( ) ;
139+ break ;
140+ }
141+ }
142+ // Append any remaining replacement string content not covered by case operations.
143+ if ( i < replacementLen ) {
144+ newReplaceString += replacement . slice ( i ) ;
145+ }
146+
147+ newReplaceString += patMatch [ 4 ] ; // suffix
148+ }
149+
150+ // Append any remaining trailing content after the final regex match.
151+ newReplaceString += replaceString . slice ( lastIndex + lastMatch . length ) ;
152+
153+ return text . replace ( regex , newReplaceString ) ;
154+ }
155+
75156 public buildReplaceString ( matches : string [ ] | null , preserveCase ?: boolean ) : string {
76157 if ( preserveCase ) {
77158 return buildReplaceStringWithCasePreserved ( matches , this . _replacePattern ) ;
0 commit comments