Skip to content

Commit 04a05bd

Browse files
authoredSep 22, 2022
feat: fix .replace() when searching string, add .replaceAll() (#222)
1 parent abf373f commit 04a05bd

File tree

3 files changed

+134
-4
lines changed

3 files changed

+134
-4
lines changed
 

‎README.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,9 @@ Same as `s.appendLeft(...)`, except that the inserted content will go *before* a
165165

166166
Same as `s.appendRight(...)`, except that the inserted content will go *before* any previous appends or prepends at `index`
167167

168-
### s.replace( regexp, substitution )
168+
### s.replace( regexpOrString, substitution )
169169

170-
String replacement with RegExp or string, a replacer function is also supported. Returns `this`.
170+
String replacement with RegExp or string. When using a RegExp, replacer function is also supported. Returns `this`.
171171

172172
```ts
173173
import MagicString from 'magic-string'
@@ -183,6 +183,11 @@ The differences from [`String.replace`]((https://developer.mozilla.org/en-US/doc
183183
- It will always match against the **original string**
184184
- It mutates the magic string state (use `.clone()` to be immutable)
185185

186+
### s.replaceAll( regexpOrString, substitution )
187+
188+
Same as `s.replace`, but replace all matched strings instead of just one.
189+
If `substitution` is a regex, then it must have the global (`g`) flag set, or a `TypeError` is thrown. Matches the behavior of the bultin [`String.property.replaceAll`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll).
190+
186191
### s.remove( start, end )
187192

188193
Removes the characters from `start` to `end` (of the original string, **not** the generated string). Removing the same content twice, or making removals that partially overlap, will cause an error. Returns `this`.

‎src/MagicString.js

+49-2
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ export default class MagicString {
736736
return this.original !== this.toString();
737737
}
738738

739-
replace(searchValue, replacement) {
739+
_replaceRegexp(searchValue, replacement) {
740740
function getReplacement(match, str) {
741741
if (typeof replacement === 'string') {
742742
return replacement.replace(/\$(\$|&|\d+)/g, (_, i) => {
@@ -759,7 +759,7 @@ export default class MagicString {
759759
}
760760
return matches;
761761
}
762-
if (typeof searchValue !== 'string' && searchValue.global) {
762+
if (searchValue.global) {
763763
const matches = matchAll(searchValue, this.original);
764764
matches.forEach((match) => {
765765
if (match.index != null)
@@ -780,4 +780,51 @@ export default class MagicString {
780780
}
781781
return this;
782782
}
783+
784+
_replaceString(string, replacement) {
785+
const { original } = this;
786+
const index = original.indexOf(string);
787+
788+
if (index !== -1) {
789+
this.overwrite(index, index + string.length, replacement);
790+
}
791+
792+
return this;
793+
}
794+
795+
replace(searchValue, replacement) {
796+
if (typeof searchValue === 'string') {
797+
return this._replaceString(searchValue, replacement);
798+
}
799+
800+
return this._replaceRegexp(searchValue, replacement);
801+
}
802+
803+
_replaceAllString(string, replacement) {
804+
const { original } = this;
805+
const stringLength = string.length;
806+
for (
807+
let index = original.indexOf(string);
808+
index !== -1;
809+
index = original.indexOf(string, index + stringLength)
810+
) {
811+
this.overwrite(index, index + stringLength, replacement);
812+
}
813+
814+
return this;
815+
}
816+
817+
replaceAll(searchValue, replacement) {
818+
if (typeof searchValue === 'string') {
819+
return this._replaceAllString(searchValue, replacement);
820+
}
821+
822+
if (!searchValue.global) {
823+
throw new TypeError(
824+
'MagicString.prototype.replaceAll called with a non-global RegExp argument'
825+
);
826+
}
827+
828+
return this._replaceRegexp(searchValue, replacement);
829+
}
783830
}

‎test/MagicString.js

+78
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,27 @@ describe('MagicString', () => {
13081308
assert.strictEqual(s.toString(), '1 3 1 2');
13091309
});
13101310

1311+
it('Should not treat string as regexp', () => {
1312+
assert.strictEqual(
1313+
new MagicString('1234').replace('.', '*').toString(),
1314+
'1234'
1315+
);
1316+
});
1317+
1318+
it('Should use substitution directly', () => {
1319+
assert.strictEqual(
1320+
new MagicString('11').replace('1', '$0$1').toString(),
1321+
'$0$11'
1322+
);
1323+
});
1324+
1325+
it('Should not search back', () => {
1326+
assert.strictEqual(
1327+
new MagicString('122121').replace('12', '21').toString(),
1328+
'212121'
1329+
);
1330+
});
1331+
13111332
it('works with global regex replace', () => {
13121333
const s = new MagicString('1 2 3 4 a b c');
13131334

@@ -1347,4 +1368,61 @@ describe('MagicString', () => {
13471368
);
13481369
});
13491370
});
1371+
1372+
describe('replaceAll', () => {
1373+
it('works with string replace', () => {
1374+
assert.strictEqual(
1375+
new MagicString('1212').replaceAll('2', '3').toString(),
1376+
'1313',
1377+
);
1378+
});
1379+
1380+
it('Should not treat string as regexp', () => {
1381+
assert.strictEqual(
1382+
new MagicString('1234').replaceAll('.', '*').toString(),
1383+
'1234'
1384+
);
1385+
});
1386+
1387+
it('Should use substitution directly', () => {
1388+
assert.strictEqual(
1389+
new MagicString('11').replaceAll('1', '$0$1').toString(),
1390+
'$0$1$0$1'
1391+
);
1392+
});
1393+
1394+
it('Should not search back', () => {
1395+
assert.strictEqual(
1396+
new MagicString('121212').replaceAll('12', '21').toString(),
1397+
'212121'
1398+
);
1399+
});
1400+
1401+
it('global regex result the same as .replace', () => {
1402+
assert.strictEqual(
1403+
new MagicString('1 2 3 4 a b c').replaceAll(/(\d)/g, 'xx$1$10').toString(),
1404+
new MagicString('1 2 3 4 a b c').replace(/(\d)/g, 'xx$1$10').toString(),
1405+
);
1406+
1407+
assert.strictEqual(
1408+
new MagicString('1 2 3 4 a b c').replaceAll(/(\d)/g, '$$').toString(),
1409+
new MagicString('1 2 3 4 a b c').replace(/(\d)/g, '$$').toString(),
1410+
);
1411+
1412+
assert.strictEqual(
1413+
new MagicString('hey this is magic').replaceAll(/(\w)(\w+)/g, (_, $1, $2) => `${$1.toUpperCase()}${$2}`).toString(),
1414+
new MagicString('hey this is magic').replace(/(\w)(\w+)/g, (_, $1, $2) => `${$1.toUpperCase()}${$2}`).toString(),
1415+
);
1416+
});
1417+
1418+
it('rejects with non-global regexp', () => {
1419+
assert.throws(
1420+
() => new MagicString('123').replaceAll(/./, ''),
1421+
{
1422+
name: 'TypeError',
1423+
message: 'MagicString.prototype.replaceAll called with a non-global RegExp argument',
1424+
},
1425+
);
1426+
});
1427+
});
13501428
});

0 commit comments

Comments
 (0)
Please sign in to comment.