Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/find/test/browser/replacePattern.test.ts
4780 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { buildReplaceStringWithCasePreserved } from '../../../../../base/common/search.js';
8
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
9
import { parseReplaceString, ReplacePattern, ReplacePiece } from '../../browser/replacePattern.js';
10
11
suite('Replace Pattern test', () => {
12
13
ensureNoDisposablesAreLeakedInTestSuite();
14
15
test('parse replace string', () => {
16
const testParse = (input: string, expectedPieces: ReplacePiece[]) => {
17
const actual = parseReplaceString(input);
18
const expected = new ReplacePattern(expectedPieces);
19
assert.deepStrictEqual(actual, expected, 'Parsing ' + input);
20
};
21
22
// no backslash => no treatment
23
testParse('hello', [ReplacePiece.staticValue('hello')]);
24
25
// \t => TAB
26
testParse('\\thello', [ReplacePiece.staticValue('\thello')]);
27
testParse('h\\tello', [ReplacePiece.staticValue('h\tello')]);
28
testParse('hello\\t', [ReplacePiece.staticValue('hello\t')]);
29
30
// \n => LF
31
testParse('\\nhello', [ReplacePiece.staticValue('\nhello')]);
32
33
// \\t => \t
34
testParse('\\\\thello', [ReplacePiece.staticValue('\\thello')]);
35
testParse('h\\\\tello', [ReplacePiece.staticValue('h\\tello')]);
36
testParse('hello\\\\t', [ReplacePiece.staticValue('hello\\t')]);
37
38
// \\\t => \TAB
39
testParse('\\\\\\thello', [ReplacePiece.staticValue('\\\thello')]);
40
41
// \\\\t => \\t
42
testParse('\\\\\\\\thello', [ReplacePiece.staticValue('\\\\thello')]);
43
44
// \ at the end => no treatment
45
testParse('hello\\', [ReplacePiece.staticValue('hello\\')]);
46
47
// \ with unknown char => no treatment
48
testParse('hello\\x', [ReplacePiece.staticValue('hello\\x')]);
49
50
// \ with back reference => no treatment
51
testParse('hello\\0', [ReplacePiece.staticValue('hello\\0')]);
52
53
testParse('hello$&', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(0)]);
54
testParse('hello$0', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(0)]);
55
testParse('hello$02', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(0), ReplacePiece.staticValue('2')]);
56
testParse('hello$1', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(1)]);
57
testParse('hello$2', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(2)]);
58
testParse('hello$9', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(9)]);
59
testParse('$9hello', [ReplacePiece.matchIndex(9), ReplacePiece.staticValue('hello')]);
60
61
testParse('hello$12', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(12)]);
62
testParse('hello$99', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(99)]);
63
testParse('hello$99a', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(99), ReplacePiece.staticValue('a')]);
64
testParse('hello$1a', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(1), ReplacePiece.staticValue('a')]);
65
testParse('hello$100', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(10), ReplacePiece.staticValue('0')]);
66
testParse('hello$100a', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(10), ReplacePiece.staticValue('0a')]);
67
testParse('hello$10a0', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(10), ReplacePiece.staticValue('a0')]);
68
testParse('hello$$', [ReplacePiece.staticValue('hello$')]);
69
testParse('hello$$0', [ReplacePiece.staticValue('hello$0')]);
70
71
testParse('hello$`', [ReplacePiece.staticValue('hello$`')]);
72
testParse('hello$\'', [ReplacePiece.staticValue('hello$\'')]);
73
});
74
75
test('parse replace string with case modifiers', () => {
76
const testParse = (input: string, expectedPieces: ReplacePiece[]) => {
77
const actual = parseReplaceString(input);
78
const expected = new ReplacePattern(expectedPieces);
79
assert.deepStrictEqual(actual, expected, 'Parsing ' + input);
80
};
81
function assertReplace(target: string, search: RegExp, replaceString: string, expected: string): void {
82
const replacePattern = parseReplaceString(replaceString);
83
const m = search.exec(target);
84
const actual = replacePattern.buildReplaceString(m);
85
86
assert.strictEqual(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`);
87
}
88
89
// \U, \u => uppercase \L, \l => lowercase \E => cancel
90
91
testParse('hello\\U$1', [ReplacePiece.staticValue('hello'), ReplacePiece.caseOps(1, ['U'])]);
92
assertReplace('func privateFunc(', /func (\w+)\(/, 'func \\U$1(', 'func PRIVATEFUNC(');
93
94
testParse('hello\\u$1', [ReplacePiece.staticValue('hello'), ReplacePiece.caseOps(1, ['u'])]);
95
assertReplace('func privateFunc(', /func (\w+)\(/, 'func \\u$1(', 'func PrivateFunc(');
96
97
testParse('hello\\L$1', [ReplacePiece.staticValue('hello'), ReplacePiece.caseOps(1, ['L'])]);
98
assertReplace('func privateFunc(', /func (\w+)\(/, 'func \\L$1(', 'func privatefunc(');
99
100
testParse('hello\\l$1', [ReplacePiece.staticValue('hello'), ReplacePiece.caseOps(1, ['l'])]);
101
assertReplace('func PrivateFunc(', /func (\w+)\(/, 'func \\l$1(', 'func privateFunc(');
102
103
testParse('hello$1\\u\\u\\U$4goodbye', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(1), ReplacePiece.caseOps(4, ['u', 'u', 'U']), ReplacePiece.staticValue('goodbye')]);
104
assertReplace('hellogooDbye', /hello(\w+)/, 'hello\\u\\u\\l\\l\\U$1', 'helloGOodBYE');
105
});
106
107
test('replace has JavaScript semantics', () => {
108
const testJSReplaceSemantics = (target: string, search: RegExp, replaceString: string, expected: string) => {
109
const replacePattern = parseReplaceString(replaceString);
110
const m = search.exec(target);
111
const actual = replacePattern.buildReplaceString(m);
112
113
assert.deepStrictEqual(actual, expected, `${target}.replace(${search}, ${replaceString})`);
114
};
115
116
testJSReplaceSemantics('hi', /hi/, 'hello', 'hi'.replace(/hi/, 'hello'));
117
testJSReplaceSemantics('hi', /hi/, '\\t', 'hi'.replace(/hi/, '\t'));
118
testJSReplaceSemantics('hi', /hi/, '\\n', 'hi'.replace(/hi/, '\n'));
119
testJSReplaceSemantics('hi', /hi/, '\\\\t', 'hi'.replace(/hi/, '\\t'));
120
testJSReplaceSemantics('hi', /hi/, '\\\\n', 'hi'.replace(/hi/, '\\n'));
121
122
// implicit capture group 0
123
testJSReplaceSemantics('hi', /hi/, 'hello$&', 'hi'.replace(/hi/, 'hello$&'));
124
testJSReplaceSemantics('hi', /hi/, 'hello$0', 'hi'.replace(/hi/, 'hello$&'));
125
testJSReplaceSemantics('hi', /hi/, 'hello$&1', 'hi'.replace(/hi/, 'hello$&1'));
126
testJSReplaceSemantics('hi', /hi/, 'hello$01', 'hi'.replace(/hi/, 'hello$&1'));
127
128
// capture groups have funny semantics in replace strings
129
// the replace string interprets $nn as a captured group only if it exists in the search regex
130
testJSReplaceSemantics('hi', /(hi)/, 'hello$10', 'hi'.replace(/(hi)/, 'hello$10'));
131
testJSReplaceSemantics('hi', /(hi)()()()()()()()()()/, 'hello$10', 'hi'.replace(/(hi)()()()()()()()()()/, 'hello$10'));
132
testJSReplaceSemantics('hi', /(hi)/, 'hello$100', 'hi'.replace(/(hi)/, 'hello$100'));
133
testJSReplaceSemantics('hi', /(hi)/, 'hello$20', 'hi'.replace(/(hi)/, 'hello$20'));
134
});
135
136
test('get replace string if given text is a complete match', () => {
137
function assertReplace(target: string, search: RegExp, replaceString: string, expected: string): void {
138
const replacePattern = parseReplaceString(replaceString);
139
const m = search.exec(target);
140
const actual = replacePattern.buildReplaceString(m);
141
142
assert.strictEqual(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`);
143
}
144
145
assertReplace('bla', /bla/, 'hello', 'hello');
146
assertReplace('bla', /(bla)/, 'hello', 'hello');
147
assertReplace('bla', /(bla)/, 'hello$0', 'hellobla');
148
149
const searchRegex = /let\s+(\w+)\s*=\s*require\s*\(\s*['"]([\w\.\-/]+)\s*['"]\s*\)\s*/;
150
assertReplace('let fs = require(\'fs\')', searchRegex, 'import * as $1 from \'$2\';', 'import * as fs from \'fs\';');
151
assertReplace('let something = require(\'fs\')', searchRegex, 'import * as $1 from \'$2\';', 'import * as something from \'fs\';');
152
assertReplace('let something = require(\'fs\')', searchRegex, 'import * as $1 from \'$1\';', 'import * as something from \'something\';');
153
assertReplace('let something = require(\'fs\')', searchRegex, 'import * as $2 from \'$1\';', 'import * as fs from \'something\';');
154
assertReplace('let something = require(\'fs\')', searchRegex, 'import * as $0 from \'$0\';', 'import * as let something = require(\'fs\') from \'let something = require(\'fs\')\';');
155
assertReplace('let fs = require(\'fs\')', searchRegex, 'import * as $1 from \'$2\';', 'import * as fs from \'fs\';');
156
assertReplace('for ()', /for(.*)/, 'cat$1', 'cat ()');
157
158
// issue #18111
159
assertReplace('HRESULT OnAmbientPropertyChange(DISPID dispid);', /\b\s{3}\b/, ' ', ' ');
160
});
161
162
test('get replace string if match is sub-string of the text', () => {
163
function assertReplace(target: string, search: RegExp, replaceString: string, expected: string): void {
164
const replacePattern = parseReplaceString(replaceString);
165
const m = search.exec(target);
166
const actual = replacePattern.buildReplaceString(m);
167
168
assert.strictEqual(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`);
169
}
170
assertReplace('this is a bla text', /bla/, 'hello', 'hello');
171
assertReplace('this is a bla text', /this(?=.*bla)/, 'that', 'that');
172
assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$1at', 'that');
173
assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$1e', 'the');
174
assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$1ere', 'there');
175
assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$1', 'th');
176
assertReplace('this is a bla text', /(th)is(?=.*bla)/, 'ma$1', 'math');
177
assertReplace('this is a bla text', /(th)is(?=.*bla)/, 'ma$1s', 'maths');
178
assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$0', 'this');
179
assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$0$1', 'thisth');
180
assertReplace('this is a bla text', /bla(?=\stext$)/, 'foo', 'foo');
181
assertReplace('this is a bla text', /b(la)(?=\stext$)/, 'f$1', 'fla');
182
assertReplace('this is a bla text', /b(la)(?=\stext$)/, 'f$0', 'fbla');
183
assertReplace('this is a bla text', /b(la)(?=\stext$)/, '$0ah', 'blaah');
184
});
185
186
test('issue #19740 Find and replace capture group/backreference inserts `undefined` instead of empty string', () => {
187
const replacePattern = parseReplaceString('a{$1}');
188
const matches = /a(z)?/.exec('abcd');
189
const actual = replacePattern.buildReplaceString(matches);
190
assert.strictEqual(actual, 'a{}');
191
});
192
193
test('buildReplaceStringWithCasePreserved test', () => {
194
function assertReplace(target: string[], replaceString: string, expected: string): void {
195
let actual: string = '';
196
actual = buildReplaceStringWithCasePreserved(target, replaceString);
197
assert.strictEqual(actual, expected);
198
}
199
200
assertReplace(['abc'], 'Def', 'def');
201
assertReplace(['Abc'], 'Def', 'Def');
202
assertReplace(['ABC'], 'Def', 'DEF');
203
assertReplace(['abc', 'Abc'], 'Def', 'def');
204
assertReplace(['Abc', 'abc'], 'Def', 'Def');
205
assertReplace(['ABC', 'abc'], 'Def', 'DEF');
206
assertReplace(['aBc', 'abc'], 'Def', 'def');
207
assertReplace(['AbC'], 'Def', 'Def');
208
assertReplace(['aBC'], 'Def', 'def');
209
assertReplace(['aBc'], 'DeF', 'deF');
210
assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar');
211
assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc');
212
assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar');
213
assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar');
214
assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR');
215
assertReplace(['foO-BAR'], 'NewFoo-NewBar', 'newFoo-NEWBAR');
216
assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar');
217
assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc');
218
assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar');
219
assertReplace(['Foo_Bar-abc'], 'newfoo_newbar-abc', 'Newfoo_newbar-abc');
220
assertReplace(['foo_Bar'], 'newfoo_newbar', 'newfoo_Newbar');
221
assertReplace(['Foo_BAR'], 'newfoo_newbar', 'Newfoo_NEWBAR');
222
});
223
224
test('preserve case', () => {
225
function assertReplace(target: string[], replaceString: string, expected: string): void {
226
const replacePattern = parseReplaceString(replaceString);
227
const actual = replacePattern.buildReplaceString(target, true);
228
assert.strictEqual(actual, expected);
229
}
230
231
assertReplace(['abc'], 'Def', 'def');
232
assertReplace(['Abc'], 'Def', 'Def');
233
assertReplace(['ABC'], 'Def', 'DEF');
234
assertReplace(['abc', 'Abc'], 'Def', 'def');
235
assertReplace(['Abc', 'abc'], 'Def', 'Def');
236
assertReplace(['ABC', 'abc'], 'Def', 'DEF');
237
assertReplace(['aBc', 'abc'], 'Def', 'def');
238
assertReplace(['AbC'], 'Def', 'Def');
239
assertReplace(['aBC'], 'Def', 'def');
240
assertReplace(['aBc'], 'DeF', 'deF');
241
assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar');
242
assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc');
243
assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar');
244
assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar');
245
assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR');
246
assertReplace(['foO-BAR'], 'NewFoo-NewBar', 'newFoo-NEWBAR');
247
assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar');
248
assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc');
249
assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar');
250
assertReplace(['Foo_Bar-abc'], 'newfoo_newbar-abc', 'Newfoo_newbar-abc');
251
assertReplace(['foo_Bar'], 'newfoo_newbar', 'newfoo_Newbar');
252
assertReplace(['foo_BAR'], 'newfoo_newbar', 'newfoo_NEWBAR');
253
});
254
});
255
256