es.string.replace.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. 'use strict';
  2. var apply = require('../internals/function-apply');
  3. var call = require('../internals/function-call');
  4. var uncurryThis = require('../internals/function-uncurry-this');
  5. var fixRegExpWellKnownSymbolLogic = require('../internals/fix-regexp-well-known-symbol-logic');
  6. var fails = require('../internals/fails');
  7. var anObject = require('../internals/an-object');
  8. var isCallable = require('../internals/is-callable');
  9. var isObject = require('../internals/is-object');
  10. var toIntegerOrInfinity = require('../internals/to-integer-or-infinity');
  11. var toLength = require('../internals/to-length');
  12. var toString = require('../internals/to-string');
  13. var requireObjectCoercible = require('../internals/require-object-coercible');
  14. var advanceStringIndex = require('../internals/advance-string-index');
  15. var getMethod = require('../internals/get-method');
  16. var getSubstitution = require('../internals/get-substitution');
  17. var getRegExpFlags = require('../internals/regexp-get-flags');
  18. var regExpExec = require('../internals/regexp-exec-abstract');
  19. var wellKnownSymbol = require('../internals/well-known-symbol');
  20. var REPLACE = wellKnownSymbol('replace');
  21. var max = Math.max;
  22. var min = Math.min;
  23. var concat = uncurryThis([].concat);
  24. var push = uncurryThis([].push);
  25. var stringIndexOf = uncurryThis(''.indexOf);
  26. var stringSlice = uncurryThis(''.slice);
  27. var maybeToString = function (it) {
  28. return it === undefined ? it : String(it);
  29. };
  30. // IE <= 11 replaces $0 with the whole match, as if it was $&
  31. // https://stackoverflow.com/questions/6024666/getting-ie-to-replace-a-regex-with-the-literal-string-0
  32. var REPLACE_KEEPS_$0 = (function () {
  33. // eslint-disable-next-line regexp/prefer-escape-replacement-dollar-char -- required for testing
  34. return 'a'.replace(/./, '$0') === '$0';
  35. })();
  36. // Safari <= 13.0.3(?) substitutes nth capture where n>m with an empty string
  37. var REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE = (function () {
  38. if (/./[REPLACE]) {
  39. return /./[REPLACE]('a', '$0') === '';
  40. }
  41. return false;
  42. })();
  43. var REPLACE_SUPPORTS_NAMED_GROUPS = !fails(function () {
  44. var re = /./;
  45. re.exec = function () {
  46. var result = [];
  47. result.groups = { a: '7' };
  48. return result;
  49. };
  50. // eslint-disable-next-line regexp/no-useless-dollar-replacements -- false positive
  51. return ''.replace(re, '$<a>') !== '7';
  52. });
  53. // @@replace logic
  54. fixRegExpWellKnownSymbolLogic('replace', function (_, nativeReplace, maybeCallNative) {
  55. var UNSAFE_SUBSTITUTE = REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE ? '$' : '$0';
  56. return [
  57. // `String.prototype.replace` method
  58. // https://tc39.es/ecma262/#sec-string.prototype.replace
  59. function replace(searchValue, replaceValue) {
  60. var O = requireObjectCoercible(this);
  61. var replacer = isObject(searchValue) ? getMethod(searchValue, REPLACE) : undefined;
  62. return replacer
  63. ? call(replacer, searchValue, O, replaceValue)
  64. : call(nativeReplace, toString(O), searchValue, replaceValue);
  65. },
  66. // `RegExp.prototype[@@replace]` method
  67. // https://tc39.es/ecma262/#sec-regexp.prototype-@@replace
  68. function (string, replaceValue) {
  69. var rx = anObject(this);
  70. var S = toString(string);
  71. if (
  72. typeof replaceValue == 'string' &&
  73. stringIndexOf(replaceValue, UNSAFE_SUBSTITUTE) === -1 &&
  74. stringIndexOf(replaceValue, '$<') === -1
  75. ) {
  76. var res = maybeCallNative(nativeReplace, rx, S, replaceValue);
  77. if (res.done) return res.value;
  78. }
  79. var functionalReplace = isCallable(replaceValue);
  80. if (!functionalReplace) replaceValue = toString(replaceValue);
  81. var flags = toString(getRegExpFlags(rx));
  82. var global = stringIndexOf(flags, 'g') !== -1;
  83. var fullUnicode;
  84. if (global) {
  85. fullUnicode = stringIndexOf(flags, 'u') !== -1;
  86. rx.lastIndex = 0;
  87. }
  88. var results = [];
  89. var result;
  90. while (true) {
  91. result = regExpExec(rx, S);
  92. if (result === null) break;
  93. push(results, result);
  94. if (!global) break;
  95. var matchStr = toString(result[0]);
  96. if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);
  97. }
  98. var accumulatedResult = '';
  99. var nextSourcePosition = 0;
  100. for (var i = 0; i < results.length; i++) {
  101. result = results[i];
  102. var matched = toString(result[0]);
  103. var position = max(min(toIntegerOrInfinity(result.index), S.length), 0);
  104. var captures = [];
  105. var replacement;
  106. // NOTE: This is equivalent to
  107. // captures = result.slice(1).map(maybeToString)
  108. // but for some reason `nativeSlice.call(result, 1, result.length)` (called in
  109. // the slice polyfill when slicing native arrays) "doesn't work" in safari 9 and
  110. // causes a crash (https://pastebin.com/N21QzeQA) when trying to debug it.
  111. for (var j = 1; j < result.length; j++) push(captures, maybeToString(result[j]));
  112. var namedCaptures = result.groups;
  113. if (functionalReplace) {
  114. var replacerArgs = concat([matched], captures, position, S);
  115. if (namedCaptures !== undefined) push(replacerArgs, namedCaptures);
  116. replacement = toString(apply(replaceValue, undefined, replacerArgs));
  117. } else {
  118. replacement = getSubstitution(matched, S, position, captures, namedCaptures, replaceValue);
  119. }
  120. if (position >= nextSourcePosition) {
  121. accumulatedResult += stringSlice(S, nextSourcePosition, position) + replacement;
  122. nextSourcePosition = position + matched.length;
  123. }
  124. }
  125. return accumulatedResult + stringSlice(S, nextSourcePosition);
  126. }
  127. ];
  128. }, !REPLACE_SUPPORTS_NAMED_GROUPS || !REPLACE_KEEPS_$0 || REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE);