Skip to content

Commit

Permalink
feat: fix .replace() when searching string, add .replaceAll() (#222)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Sep 22, 2022
1 parent abf373f commit 04a05bd
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 4 deletions.
9 changes: 7 additions & 2 deletions README.md
Expand Up @@ -165,9 +165,9 @@ Same as `s.appendLeft(...)`, except that the inserted content will go *before* a

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

### s.replace( regexp, substitution )
### s.replace( regexpOrString, substitution )

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

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

### s.replaceAll( regexpOrString, substitution )

Same as `s.replace`, but replace all matched strings instead of just one.
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).

### s.remove( start, end )

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`.
Expand Down
51 changes: 49 additions & 2 deletions src/MagicString.js
Expand Up @@ -736,7 +736,7 @@ export default class MagicString {
return this.original !== this.toString();
}

replace(searchValue, replacement) {
_replaceRegexp(searchValue, replacement) {
function getReplacement(match, str) {
if (typeof replacement === 'string') {
return replacement.replace(/\$(\$|&|\d+)/g, (_, i) => {
Expand All @@ -759,7 +759,7 @@ export default class MagicString {
}
return matches;
}
if (typeof searchValue !== 'string' && searchValue.global) {
if (searchValue.global) {
const matches = matchAll(searchValue, this.original);
matches.forEach((match) => {
if (match.index != null)
Expand All @@ -780,4 +780,51 @@ export default class MagicString {
}
return this;
}

_replaceString(string, replacement) {
const { original } = this;
const index = original.indexOf(string);

if (index !== -1) {
this.overwrite(index, index + string.length, replacement);
}

return this;
}

replace(searchValue, replacement) {
if (typeof searchValue === 'string') {
return this._replaceString(searchValue, replacement);
}

return this._replaceRegexp(searchValue, replacement);
}

_replaceAllString(string, replacement) {
const { original } = this;
const stringLength = string.length;
for (
let index = original.indexOf(string);
index !== -1;
index = original.indexOf(string, index + stringLength)
) {
this.overwrite(index, index + stringLength, replacement);
}

return this;
}

replaceAll(searchValue, replacement) {
if (typeof searchValue === 'string') {
return this._replaceAllString(searchValue, replacement);
}

if (!searchValue.global) {
throw new TypeError(
'MagicString.prototype.replaceAll called with a non-global RegExp argument'
);
}

return this._replaceRegexp(searchValue, replacement);
}
}
78 changes: 78 additions & 0 deletions test/MagicString.js
Expand Up @@ -1308,6 +1308,27 @@ describe('MagicString', () => {
assert.strictEqual(s.toString(), '1 3 1 2');
});

it('Should not treat string as regexp', () => {
assert.strictEqual(
new MagicString('1234').replace('.', '*').toString(),
'1234'
);
});

it('Should use substitution directly', () => {
assert.strictEqual(
new MagicString('11').replace('1', '$0$1').toString(),
'$0$11'
);
});

it('Should not search back', () => {
assert.strictEqual(
new MagicString('122121').replace('12', '21').toString(),
'212121'
);
});

it('works with global regex replace', () => {
const s = new MagicString('1 2 3 4 a b c');

Expand Down Expand Up @@ -1347,4 +1368,61 @@ describe('MagicString', () => {
);
});
});

describe('replaceAll', () => {
it('works with string replace', () => {
assert.strictEqual(
new MagicString('1212').replaceAll('2', '3').toString(),
'1313',
);
});

it('Should not treat string as regexp', () => {
assert.strictEqual(
new MagicString('1234').replaceAll('.', '*').toString(),
'1234'
);
});

it('Should use substitution directly', () => {
assert.strictEqual(
new MagicString('11').replaceAll('1', '$0$1').toString(),
'$0$1$0$1'
);
});

it('Should not search back', () => {
assert.strictEqual(
new MagicString('121212').replaceAll('12', '21').toString(),
'212121'
);
});

it('global regex result the same as .replace', () => {
assert.strictEqual(
new MagicString('1 2 3 4 a b c').replaceAll(/(\d)/g, 'xx$1$10').toString(),
new MagicString('1 2 3 4 a b c').replace(/(\d)/g, 'xx$1$10').toString(),
);

assert.strictEqual(
new MagicString('1 2 3 4 a b c').replaceAll(/(\d)/g, '$$').toString(),
new MagicString('1 2 3 4 a b c').replace(/(\d)/g, '$$').toString(),
);

assert.strictEqual(
new MagicString('hey this is magic').replaceAll(/(\w)(\w+)/g, (_, $1, $2) => `${$1.toUpperCase()}${$2}`).toString(),
new MagicString('hey this is magic').replace(/(\w)(\w+)/g, (_, $1, $2) => `${$1.toUpperCase()}${$2}`).toString(),
);
});

it('rejects with non-global regexp', () => {
assert.throws(
() => new MagicString('123').replaceAll(/./, ''),
{
name: 'TypeError',
message: 'MagicString.prototype.replaceAll called with a non-global RegExp argument',
},
);
});
});
});

0 comments on commit 04a05bd

Please sign in to comment.