string.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.longestCommonPrefix = longestCommonPrefix;
  4. exports.longestCommonSuffix = longestCommonSuffix;
  5. exports.replacePrefix = replacePrefix;
  6. exports.replaceSuffix = replaceSuffix;
  7. exports.removePrefix = removePrefix;
  8. exports.removeSuffix = removeSuffix;
  9. exports.maximumOverlap = maximumOverlap;
  10. exports.hasOnlyWinLineEndings = hasOnlyWinLineEndings;
  11. exports.hasOnlyUnixLineEndings = hasOnlyUnixLineEndings;
  12. exports.trailingWs = trailingWs;
  13. exports.leadingWs = leadingWs;
  14. function longestCommonPrefix(str1, str2) {
  15. var i;
  16. for (i = 0; i < str1.length && i < str2.length; i++) {
  17. if (str1[i] != str2[i]) {
  18. return str1.slice(0, i);
  19. }
  20. }
  21. return str1.slice(0, i);
  22. }
  23. function longestCommonSuffix(str1, str2) {
  24. var i;
  25. // Unlike longestCommonPrefix, we need a special case to handle all scenarios
  26. // where we return the empty string since str1.slice(-0) will return the
  27. // entire string.
  28. if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) {
  29. return '';
  30. }
  31. for (i = 0; i < str1.length && i < str2.length; i++) {
  32. if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) {
  33. return str1.slice(-i);
  34. }
  35. }
  36. return str1.slice(-i);
  37. }
  38. function replacePrefix(string, oldPrefix, newPrefix) {
  39. if (string.slice(0, oldPrefix.length) != oldPrefix) {
  40. throw Error("string ".concat(JSON.stringify(string), " doesn't start with prefix ").concat(JSON.stringify(oldPrefix), "; this is a bug"));
  41. }
  42. return newPrefix + string.slice(oldPrefix.length);
  43. }
  44. function replaceSuffix(string, oldSuffix, newSuffix) {
  45. if (!oldSuffix) {
  46. return string + newSuffix;
  47. }
  48. if (string.slice(-oldSuffix.length) != oldSuffix) {
  49. throw Error("string ".concat(JSON.stringify(string), " doesn't end with suffix ").concat(JSON.stringify(oldSuffix), "; this is a bug"));
  50. }
  51. return string.slice(0, -oldSuffix.length) + newSuffix;
  52. }
  53. function removePrefix(string, oldPrefix) {
  54. return replacePrefix(string, oldPrefix, '');
  55. }
  56. function removeSuffix(string, oldSuffix) {
  57. return replaceSuffix(string, oldSuffix, '');
  58. }
  59. function maximumOverlap(string1, string2) {
  60. return string2.slice(0, overlapCount(string1, string2));
  61. }
  62. // Nicked from https://stackoverflow.com/a/60422853/1709587
  63. function overlapCount(a, b) {
  64. // Deal with cases where the strings differ in length
  65. var startA = 0;
  66. if (a.length > b.length) {
  67. startA = a.length - b.length;
  68. }
  69. var endB = b.length;
  70. if (a.length < b.length) {
  71. endB = a.length;
  72. }
  73. // Create a back-reference for each index
  74. // that should be followed in case of a mismatch.
  75. // We only need B to make these references:
  76. var map = Array(endB);
  77. var k = 0; // Index that lags behind j
  78. map[0] = 0;
  79. for (var j = 1; j < endB; j++) {
  80. if (b[j] == b[k]) {
  81. map[j] = map[k]; // skip over the same character (optional optimisation)
  82. }
  83. else {
  84. map[j] = k;
  85. }
  86. while (k > 0 && b[j] != b[k]) {
  87. k = map[k];
  88. }
  89. if (b[j] == b[k]) {
  90. k++;
  91. }
  92. }
  93. // Phase 2: use these references while iterating over A
  94. k = 0;
  95. for (var i = startA; i < a.length; i++) {
  96. while (k > 0 && a[i] != b[k]) {
  97. k = map[k];
  98. }
  99. if (a[i] == b[k]) {
  100. k++;
  101. }
  102. }
  103. return k;
  104. }
  105. /**
  106. * Returns true if the string consistently uses Windows line endings.
  107. */
  108. function hasOnlyWinLineEndings(string) {
  109. return string.includes('\r\n') && !string.startsWith('\n') && !string.match(/[^\r]\n/);
  110. }
  111. /**
  112. * Returns true if the string consistently uses Unix line endings.
  113. */
  114. function hasOnlyUnixLineEndings(string) {
  115. return !string.includes('\r\n') && string.includes('\n');
  116. }
  117. function trailingWs(string) {
  118. // Yes, this looks overcomplicated and dumb - why not replace the whole function with
  119. // return string match(/\s*$/)[0]
  120. // you ask? Because:
  121. // 1. the trap described at https://markamery.com/blog/quadratic-time-regexes/ would mean doing
  122. // this would cause this function to take O(n²) time in the worst case (specifically when
  123. // there is a massive run of NON-TRAILING whitespace in `string`), and
  124. // 2. the fix proposed in the same blog post, of using a negative lookbehind, is incompatible
  125. // with old Safari versions that we'd like to not break if possible (see
  126. // https://github.com/kpdecker/jsdiff/pull/550)
  127. // It feels absurd to do this with an explicit loop instead of a regex, but I really can't see a
  128. // better way that doesn't result in broken behaviour.
  129. var i;
  130. for (i = string.length - 1; i >= 0; i--) {
  131. if (!string[i].match(/\s/)) {
  132. break;
  133. }
  134. }
  135. return string.substring(i + 1);
  136. }
  137. function leadingWs(string) {
  138. // Thankfully the annoying considerations described in trailingWs don't apply here:
  139. var match = string.match(/^\s*/);
  140. return match ? match[0] : '';
  141. }