Path: blob/main/src/vs/editor/contrib/find/test/browser/replacePattern.test.ts
4780 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { buildReplaceStringWithCasePreserved } from '../../../../../base/common/search.js';7import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';8import { parseReplaceString, ReplacePattern, ReplacePiece } from '../../browser/replacePattern.js';910suite('Replace Pattern test', () => {1112ensureNoDisposablesAreLeakedInTestSuite();1314test('parse replace string', () => {15const testParse = (input: string, expectedPieces: ReplacePiece[]) => {16const actual = parseReplaceString(input);17const expected = new ReplacePattern(expectedPieces);18assert.deepStrictEqual(actual, expected, 'Parsing ' + input);19};2021// no backslash => no treatment22testParse('hello', [ReplacePiece.staticValue('hello')]);2324// \t => TAB25testParse('\\thello', [ReplacePiece.staticValue('\thello')]);26testParse('h\\tello', [ReplacePiece.staticValue('h\tello')]);27testParse('hello\\t', [ReplacePiece.staticValue('hello\t')]);2829// \n => LF30testParse('\\nhello', [ReplacePiece.staticValue('\nhello')]);3132// \\t => \t33testParse('\\\\thello', [ReplacePiece.staticValue('\\thello')]);34testParse('h\\\\tello', [ReplacePiece.staticValue('h\\tello')]);35testParse('hello\\\\t', [ReplacePiece.staticValue('hello\\t')]);3637// \\\t => \TAB38testParse('\\\\\\thello', [ReplacePiece.staticValue('\\\thello')]);3940// \\\\t => \\t41testParse('\\\\\\\\thello', [ReplacePiece.staticValue('\\\\thello')]);4243// \ at the end => no treatment44testParse('hello\\', [ReplacePiece.staticValue('hello\\')]);4546// \ with unknown char => no treatment47testParse('hello\\x', [ReplacePiece.staticValue('hello\\x')]);4849// \ with back reference => no treatment50testParse('hello\\0', [ReplacePiece.staticValue('hello\\0')]);5152testParse('hello$&', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(0)]);53testParse('hello$0', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(0)]);54testParse('hello$02', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(0), ReplacePiece.staticValue('2')]);55testParse('hello$1', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(1)]);56testParse('hello$2', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(2)]);57testParse('hello$9', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(9)]);58testParse('$9hello', [ReplacePiece.matchIndex(9), ReplacePiece.staticValue('hello')]);5960testParse('hello$12', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(12)]);61testParse('hello$99', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(99)]);62testParse('hello$99a', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(99), ReplacePiece.staticValue('a')]);63testParse('hello$1a', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(1), ReplacePiece.staticValue('a')]);64testParse('hello$100', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(10), ReplacePiece.staticValue('0')]);65testParse('hello$100a', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(10), ReplacePiece.staticValue('0a')]);66testParse('hello$10a0', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(10), ReplacePiece.staticValue('a0')]);67testParse('hello$$', [ReplacePiece.staticValue('hello$')]);68testParse('hello$$0', [ReplacePiece.staticValue('hello$0')]);6970testParse('hello$`', [ReplacePiece.staticValue('hello$`')]);71testParse('hello$\'', [ReplacePiece.staticValue('hello$\'')]);72});7374test('parse replace string with case modifiers', () => {75const testParse = (input: string, expectedPieces: ReplacePiece[]) => {76const actual = parseReplaceString(input);77const expected = new ReplacePattern(expectedPieces);78assert.deepStrictEqual(actual, expected, 'Parsing ' + input);79};80function assertReplace(target: string, search: RegExp, replaceString: string, expected: string): void {81const replacePattern = parseReplaceString(replaceString);82const m = search.exec(target);83const actual = replacePattern.buildReplaceString(m);8485assert.strictEqual(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`);86}8788// \U, \u => uppercase \L, \l => lowercase \E => cancel8990testParse('hello\\U$1', [ReplacePiece.staticValue('hello'), ReplacePiece.caseOps(1, ['U'])]);91assertReplace('func privateFunc(', /func (\w+)\(/, 'func \\U$1(', 'func PRIVATEFUNC(');9293testParse('hello\\u$1', [ReplacePiece.staticValue('hello'), ReplacePiece.caseOps(1, ['u'])]);94assertReplace('func privateFunc(', /func (\w+)\(/, 'func \\u$1(', 'func PrivateFunc(');9596testParse('hello\\L$1', [ReplacePiece.staticValue('hello'), ReplacePiece.caseOps(1, ['L'])]);97assertReplace('func privateFunc(', /func (\w+)\(/, 'func \\L$1(', 'func privatefunc(');9899testParse('hello\\l$1', [ReplacePiece.staticValue('hello'), ReplacePiece.caseOps(1, ['l'])]);100assertReplace('func PrivateFunc(', /func (\w+)\(/, 'func \\l$1(', 'func privateFunc(');101102testParse('hello$1\\u\\u\\U$4goodbye', [ReplacePiece.staticValue('hello'), ReplacePiece.matchIndex(1), ReplacePiece.caseOps(4, ['u', 'u', 'U']), ReplacePiece.staticValue('goodbye')]);103assertReplace('hellogooDbye', /hello(\w+)/, 'hello\\u\\u\\l\\l\\U$1', 'helloGOodBYE');104});105106test('replace has JavaScript semantics', () => {107const testJSReplaceSemantics = (target: string, search: RegExp, replaceString: string, expected: string) => {108const replacePattern = parseReplaceString(replaceString);109const m = search.exec(target);110const actual = replacePattern.buildReplaceString(m);111112assert.deepStrictEqual(actual, expected, `${target}.replace(${search}, ${replaceString})`);113};114115testJSReplaceSemantics('hi', /hi/, 'hello', 'hi'.replace(/hi/, 'hello'));116testJSReplaceSemantics('hi', /hi/, '\\t', 'hi'.replace(/hi/, '\t'));117testJSReplaceSemantics('hi', /hi/, '\\n', 'hi'.replace(/hi/, '\n'));118testJSReplaceSemantics('hi', /hi/, '\\\\t', 'hi'.replace(/hi/, '\\t'));119testJSReplaceSemantics('hi', /hi/, '\\\\n', 'hi'.replace(/hi/, '\\n'));120121// implicit capture group 0122testJSReplaceSemantics('hi', /hi/, 'hello$&', 'hi'.replace(/hi/, 'hello$&'));123testJSReplaceSemantics('hi', /hi/, 'hello$0', 'hi'.replace(/hi/, 'hello$&'));124testJSReplaceSemantics('hi', /hi/, 'hello$&1', 'hi'.replace(/hi/, 'hello$&1'));125testJSReplaceSemantics('hi', /hi/, 'hello$01', 'hi'.replace(/hi/, 'hello$&1'));126127// capture groups have funny semantics in replace strings128// the replace string interprets $nn as a captured group only if it exists in the search regex129testJSReplaceSemantics('hi', /(hi)/, 'hello$10', 'hi'.replace(/(hi)/, 'hello$10'));130testJSReplaceSemantics('hi', /(hi)()()()()()()()()()/, 'hello$10', 'hi'.replace(/(hi)()()()()()()()()()/, 'hello$10'));131testJSReplaceSemantics('hi', /(hi)/, 'hello$100', 'hi'.replace(/(hi)/, 'hello$100'));132testJSReplaceSemantics('hi', /(hi)/, 'hello$20', 'hi'.replace(/(hi)/, 'hello$20'));133});134135test('get replace string if given text is a complete match', () => {136function assertReplace(target: string, search: RegExp, replaceString: string, expected: string): void {137const replacePattern = parseReplaceString(replaceString);138const m = search.exec(target);139const actual = replacePattern.buildReplaceString(m);140141assert.strictEqual(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`);142}143144assertReplace('bla', /bla/, 'hello', 'hello');145assertReplace('bla', /(bla)/, 'hello', 'hello');146assertReplace('bla', /(bla)/, 'hello$0', 'hellobla');147148const searchRegex = /let\s+(\w+)\s*=\s*require\s*\(\s*['"]([\w\.\-/]+)\s*['"]\s*\)\s*/;149assertReplace('let fs = require(\'fs\')', searchRegex, 'import * as $1 from \'$2\';', 'import * as fs from \'fs\';');150assertReplace('let something = require(\'fs\')', searchRegex, 'import * as $1 from \'$2\';', 'import * as something from \'fs\';');151assertReplace('let something = require(\'fs\')', searchRegex, 'import * as $1 from \'$1\';', 'import * as something from \'something\';');152assertReplace('let something = require(\'fs\')', searchRegex, 'import * as $2 from \'$1\';', 'import * as fs from \'something\';');153assertReplace('let something = require(\'fs\')', searchRegex, 'import * as $0 from \'$0\';', 'import * as let something = require(\'fs\') from \'let something = require(\'fs\')\';');154assertReplace('let fs = require(\'fs\')', searchRegex, 'import * as $1 from \'$2\';', 'import * as fs from \'fs\';');155assertReplace('for ()', /for(.*)/, 'cat$1', 'cat ()');156157// issue #18111158assertReplace('HRESULT OnAmbientPropertyChange(DISPID dispid);', /\b\s{3}\b/, ' ', ' ');159});160161test('get replace string if match is sub-string of the text', () => {162function assertReplace(target: string, search: RegExp, replaceString: string, expected: string): void {163const replacePattern = parseReplaceString(replaceString);164const m = search.exec(target);165const actual = replacePattern.buildReplaceString(m);166167assert.strictEqual(actual, expected, `${target}.replace(${search}, ${replaceString}) === ${expected}`);168}169assertReplace('this is a bla text', /bla/, 'hello', 'hello');170assertReplace('this is a bla text', /this(?=.*bla)/, 'that', 'that');171assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$1at', 'that');172assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$1e', 'the');173assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$1ere', 'there');174assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$1', 'th');175assertReplace('this is a bla text', /(th)is(?=.*bla)/, 'ma$1', 'math');176assertReplace('this is a bla text', /(th)is(?=.*bla)/, 'ma$1s', 'maths');177assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$0', 'this');178assertReplace('this is a bla text', /(th)is(?=.*bla)/, '$0$1', 'thisth');179assertReplace('this is a bla text', /bla(?=\stext$)/, 'foo', 'foo');180assertReplace('this is a bla text', /b(la)(?=\stext$)/, 'f$1', 'fla');181assertReplace('this is a bla text', /b(la)(?=\stext$)/, 'f$0', 'fbla');182assertReplace('this is a bla text', /b(la)(?=\stext$)/, '$0ah', 'blaah');183});184185test('issue #19740 Find and replace capture group/backreference inserts `undefined` instead of empty string', () => {186const replacePattern = parseReplaceString('a{$1}');187const matches = /a(z)?/.exec('abcd');188const actual = replacePattern.buildReplaceString(matches);189assert.strictEqual(actual, 'a{}');190});191192test('buildReplaceStringWithCasePreserved test', () => {193function assertReplace(target: string[], replaceString: string, expected: string): void {194let actual: string = '';195actual = buildReplaceStringWithCasePreserved(target, replaceString);196assert.strictEqual(actual, expected);197}198199assertReplace(['abc'], 'Def', 'def');200assertReplace(['Abc'], 'Def', 'Def');201assertReplace(['ABC'], 'Def', 'DEF');202assertReplace(['abc', 'Abc'], 'Def', 'def');203assertReplace(['Abc', 'abc'], 'Def', 'Def');204assertReplace(['ABC', 'abc'], 'Def', 'DEF');205assertReplace(['aBc', 'abc'], 'Def', 'def');206assertReplace(['AbC'], 'Def', 'Def');207assertReplace(['aBC'], 'Def', 'def');208assertReplace(['aBc'], 'DeF', 'deF');209assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar');210assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc');211assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar');212assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar');213assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR');214assertReplace(['foO-BAR'], 'NewFoo-NewBar', 'newFoo-NEWBAR');215assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar');216assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc');217assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar');218assertReplace(['Foo_Bar-abc'], 'newfoo_newbar-abc', 'Newfoo_newbar-abc');219assertReplace(['foo_Bar'], 'newfoo_newbar', 'newfoo_Newbar');220assertReplace(['Foo_BAR'], 'newfoo_newbar', 'Newfoo_NEWBAR');221});222223test('preserve case', () => {224function assertReplace(target: string[], replaceString: string, expected: string): void {225const replacePattern = parseReplaceString(replaceString);226const actual = replacePattern.buildReplaceString(target, true);227assert.strictEqual(actual, expected);228}229230assertReplace(['abc'], 'Def', 'def');231assertReplace(['Abc'], 'Def', 'Def');232assertReplace(['ABC'], 'Def', 'DEF');233assertReplace(['abc', 'Abc'], 'Def', 'def');234assertReplace(['Abc', 'abc'], 'Def', 'Def');235assertReplace(['ABC', 'abc'], 'Def', 'DEF');236assertReplace(['aBc', 'abc'], 'Def', 'def');237assertReplace(['AbC'], 'Def', 'Def');238assertReplace(['aBC'], 'Def', 'def');239assertReplace(['aBc'], 'DeF', 'deF');240assertReplace(['Foo-Bar'], 'newfoo-newbar', 'Newfoo-Newbar');241assertReplace(['Foo-Bar-Abc'], 'newfoo-newbar-newabc', 'Newfoo-Newbar-Newabc');242assertReplace(['Foo-Bar-abc'], 'newfoo-newbar', 'Newfoo-newbar');243assertReplace(['foo-Bar'], 'newfoo-newbar', 'newfoo-Newbar');244assertReplace(['foo-BAR'], 'newfoo-newbar', 'newfoo-NEWBAR');245assertReplace(['foO-BAR'], 'NewFoo-NewBar', 'newFoo-NEWBAR');246assertReplace(['Foo_Bar'], 'newfoo_newbar', 'Newfoo_Newbar');247assertReplace(['Foo_Bar_Abc'], 'newfoo_newbar_newabc', 'Newfoo_Newbar_Newabc');248assertReplace(['Foo_Bar_abc'], 'newfoo_newbar', 'Newfoo_newbar');249assertReplace(['Foo_Bar-abc'], 'newfoo_newbar-abc', 'Newfoo_newbar-abc');250assertReplace(['foo_Bar'], 'newfoo_newbar', 'newfoo_Newbar');251assertReplace(['foo_BAR'], 'newfoo_newbar', 'newfoo_NEWBAR');252});253});254255256