index.es6.js 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041
  1. function Diff() {}
  2. Diff.prototype = {
  3. diff: function diff(oldString, newString) {
  4. var _options$timeout;
  5. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  6. var callback = options.callback;
  7. if (typeof options === 'function') {
  8. callback = options;
  9. options = {};
  10. }
  11. var self = this;
  12. function done(value) {
  13. value = self.postProcess(value, options);
  14. if (callback) {
  15. setTimeout(function () {
  16. callback(value);
  17. }, 0);
  18. return true;
  19. } else {
  20. return value;
  21. }
  22. }
  23. // Allow subclasses to massage the input prior to running
  24. oldString = this.castInput(oldString, options);
  25. newString = this.castInput(newString, options);
  26. oldString = this.removeEmpty(this.tokenize(oldString, options));
  27. newString = this.removeEmpty(this.tokenize(newString, options));
  28. var newLen = newString.length,
  29. oldLen = oldString.length;
  30. var editLength = 1;
  31. var maxEditLength = newLen + oldLen;
  32. if (options.maxEditLength != null) {
  33. maxEditLength = Math.min(maxEditLength, options.maxEditLength);
  34. }
  35. var maxExecutionTime = (_options$timeout = options.timeout) !== null && _options$timeout !== void 0 ? _options$timeout : Infinity;
  36. var abortAfterTimestamp = Date.now() + maxExecutionTime;
  37. var bestPath = [{
  38. oldPos: -1,
  39. lastComponent: undefined
  40. }];
  41. // Seed editLength = 0, i.e. the content starts with the same values
  42. var newPos = this.extractCommon(bestPath[0], newString, oldString, 0, options);
  43. if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
  44. // Identity per the equality and tokenizer
  45. return done(buildValues(self, bestPath[0].lastComponent, newString, oldString, self.useLongestToken));
  46. }
  47. // Once we hit the right edge of the edit graph on some diagonal k, we can
  48. // definitely reach the end of the edit graph in no more than k edits, so
  49. // there's no point in considering any moves to diagonal k+1 any more (from
  50. // which we're guaranteed to need at least k+1 more edits).
  51. // Similarly, once we've reached the bottom of the edit graph, there's no
  52. // point considering moves to lower diagonals.
  53. // We record this fact by setting minDiagonalToConsider and
  54. // maxDiagonalToConsider to some finite value once we've hit the edge of
  55. // the edit graph.
  56. // This optimization is not faithful to the original algorithm presented in
  57. // Myers's paper, which instead pointlessly extends D-paths off the end of
  58. // the edit graph - see page 7 of Myers's paper which notes this point
  59. // explicitly and illustrates it with a diagram. This has major performance
  60. // implications for some common scenarios. For instance, to compute a diff
  61. // where the new text simply appends d characters on the end of the
  62. // original text of length n, the true Myers algorithm will take O(n+d^2)
  63. // time while this optimization needs only O(n+d) time.
  64. var minDiagonalToConsider = -Infinity,
  65. maxDiagonalToConsider = Infinity;
  66. // Main worker method. checks all permutations of a given edit length for acceptance.
  67. function execEditLength() {
  68. for (var diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
  69. var basePath = void 0;
  70. var removePath = bestPath[diagonalPath - 1],
  71. addPath = bestPath[diagonalPath + 1];
  72. if (removePath) {
  73. // No one else is going to attempt to use this value, clear it
  74. bestPath[diagonalPath - 1] = undefined;
  75. }
  76. var canAdd = false;
  77. if (addPath) {
  78. // what newPos will be after we do an insertion:
  79. var addPathNewPos = addPath.oldPos - diagonalPath;
  80. canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
  81. }
  82. var canRemove = removePath && removePath.oldPos + 1 < oldLen;
  83. if (!canAdd && !canRemove) {
  84. // If this path is a terminal then prune
  85. bestPath[diagonalPath] = undefined;
  86. continue;
  87. }
  88. // Select the diagonal that we want to branch from. We select the prior
  89. // path whose position in the old string is the farthest from the origin
  90. // and does not pass the bounds of the diff graph
  91. if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) {
  92. basePath = self.addToPath(addPath, true, false, 0, options);
  93. } else {
  94. basePath = self.addToPath(removePath, false, true, 1, options);
  95. }
  96. newPos = self.extractCommon(basePath, newString, oldString, diagonalPath, options);
  97. if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
  98. // If we have hit the end of both strings, then we are done
  99. return done(buildValues(self, basePath.lastComponent, newString, oldString, self.useLongestToken));
  100. } else {
  101. bestPath[diagonalPath] = basePath;
  102. if (basePath.oldPos + 1 >= oldLen) {
  103. maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
  104. }
  105. if (newPos + 1 >= newLen) {
  106. minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
  107. }
  108. }
  109. }
  110. editLength++;
  111. }
  112. // Performs the length of edit iteration. Is a bit fugly as this has to support the
  113. // sync and async mode which is never fun. Loops over execEditLength until a value
  114. // is produced, or until the edit length exceeds options.maxEditLength (if given),
  115. // in which case it will return undefined.
  116. if (callback) {
  117. (function exec() {
  118. setTimeout(function () {
  119. if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
  120. return callback();
  121. }
  122. if (!execEditLength()) {
  123. exec();
  124. }
  125. }, 0);
  126. })();
  127. } else {
  128. while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
  129. var ret = execEditLength();
  130. if (ret) {
  131. return ret;
  132. }
  133. }
  134. }
  135. },
  136. addToPath: function addToPath(path, added, removed, oldPosInc, options) {
  137. var last = path.lastComponent;
  138. if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
  139. return {
  140. oldPos: path.oldPos + oldPosInc,
  141. lastComponent: {
  142. count: last.count + 1,
  143. added: added,
  144. removed: removed,
  145. previousComponent: last.previousComponent
  146. }
  147. };
  148. } else {
  149. return {
  150. oldPos: path.oldPos + oldPosInc,
  151. lastComponent: {
  152. count: 1,
  153. added: added,
  154. removed: removed,
  155. previousComponent: last
  156. }
  157. };
  158. }
  159. },
  160. extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath, options) {
  161. var newLen = newString.length,
  162. oldLen = oldString.length,
  163. oldPos = basePath.oldPos,
  164. newPos = oldPos - diagonalPath,
  165. commonCount = 0;
  166. while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldString[oldPos + 1], newString[newPos + 1], options)) {
  167. newPos++;
  168. oldPos++;
  169. commonCount++;
  170. if (options.oneChangePerToken) {
  171. basePath.lastComponent = {
  172. count: 1,
  173. previousComponent: basePath.lastComponent,
  174. added: false,
  175. removed: false
  176. };
  177. }
  178. }
  179. if (commonCount && !options.oneChangePerToken) {
  180. basePath.lastComponent = {
  181. count: commonCount,
  182. previousComponent: basePath.lastComponent,
  183. added: false,
  184. removed: false
  185. };
  186. }
  187. basePath.oldPos = oldPos;
  188. return newPos;
  189. },
  190. equals: function equals(left, right, options) {
  191. if (options.comparator) {
  192. return options.comparator(left, right);
  193. } else {
  194. return left === right || options.ignoreCase && left.toLowerCase() === right.toLowerCase();
  195. }
  196. },
  197. removeEmpty: function removeEmpty(array) {
  198. var ret = [];
  199. for (var i = 0; i < array.length; i++) {
  200. if (array[i]) {
  201. ret.push(array[i]);
  202. }
  203. }
  204. return ret;
  205. },
  206. castInput: function castInput(value) {
  207. return value;
  208. },
  209. tokenize: function tokenize(value) {
  210. return Array.from(value);
  211. },
  212. join: function join(chars) {
  213. return chars.join('');
  214. },
  215. postProcess: function postProcess(changeObjects) {
  216. return changeObjects;
  217. }
  218. };
  219. function buildValues(diff, lastComponent, newString, oldString, useLongestToken) {
  220. // First we convert our linked list of components in reverse order to an
  221. // array in the right order:
  222. var components = [];
  223. var nextComponent;
  224. while (lastComponent) {
  225. components.push(lastComponent);
  226. nextComponent = lastComponent.previousComponent;
  227. delete lastComponent.previousComponent;
  228. lastComponent = nextComponent;
  229. }
  230. components.reverse();
  231. var componentPos = 0,
  232. componentLen = components.length,
  233. newPos = 0,
  234. oldPos = 0;
  235. for (; componentPos < componentLen; componentPos++) {
  236. var component = components[componentPos];
  237. if (!component.removed) {
  238. if (!component.added && useLongestToken) {
  239. var value = newString.slice(newPos, newPos + component.count);
  240. value = value.map(function (value, i) {
  241. var oldValue = oldString[oldPos + i];
  242. return oldValue.length > value.length ? oldValue : value;
  243. });
  244. component.value = diff.join(value);
  245. } else {
  246. component.value = diff.join(newString.slice(newPos, newPos + component.count));
  247. }
  248. newPos += component.count;
  249. // Common case
  250. if (!component.added) {
  251. oldPos += component.count;
  252. }
  253. } else {
  254. component.value = diff.join(oldString.slice(oldPos, oldPos + component.count));
  255. oldPos += component.count;
  256. }
  257. }
  258. return components;
  259. }
  260. var characterDiff = new Diff();
  261. function diffChars(oldStr, newStr, options) {
  262. return characterDiff.diff(oldStr, newStr, options);
  263. }
  264. function longestCommonPrefix(str1, str2) {
  265. var i;
  266. for (i = 0; i < str1.length && i < str2.length; i++) {
  267. if (str1[i] != str2[i]) {
  268. return str1.slice(0, i);
  269. }
  270. }
  271. return str1.slice(0, i);
  272. }
  273. function longestCommonSuffix(str1, str2) {
  274. var i;
  275. // Unlike longestCommonPrefix, we need a special case to handle all scenarios
  276. // where we return the empty string since str1.slice(-0) will return the
  277. // entire string.
  278. if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) {
  279. return '';
  280. }
  281. for (i = 0; i < str1.length && i < str2.length; i++) {
  282. if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) {
  283. return str1.slice(-i);
  284. }
  285. }
  286. return str1.slice(-i);
  287. }
  288. function replacePrefix(string, oldPrefix, newPrefix) {
  289. if (string.slice(0, oldPrefix.length) != oldPrefix) {
  290. throw Error("string ".concat(JSON.stringify(string), " doesn't start with prefix ").concat(JSON.stringify(oldPrefix), "; this is a bug"));
  291. }
  292. return newPrefix + string.slice(oldPrefix.length);
  293. }
  294. function replaceSuffix(string, oldSuffix, newSuffix) {
  295. if (!oldSuffix) {
  296. return string + newSuffix;
  297. }
  298. if (string.slice(-oldSuffix.length) != oldSuffix) {
  299. throw Error("string ".concat(JSON.stringify(string), " doesn't end with suffix ").concat(JSON.stringify(oldSuffix), "; this is a bug"));
  300. }
  301. return string.slice(0, -oldSuffix.length) + newSuffix;
  302. }
  303. function removePrefix(string, oldPrefix) {
  304. return replacePrefix(string, oldPrefix, '');
  305. }
  306. function removeSuffix(string, oldSuffix) {
  307. return replaceSuffix(string, oldSuffix, '');
  308. }
  309. function maximumOverlap(string1, string2) {
  310. return string2.slice(0, overlapCount(string1, string2));
  311. }
  312. // Nicked from https://stackoverflow.com/a/60422853/1709587
  313. function overlapCount(a, b) {
  314. // Deal with cases where the strings differ in length
  315. var startA = 0;
  316. if (a.length > b.length) {
  317. startA = a.length - b.length;
  318. }
  319. var endB = b.length;
  320. if (a.length < b.length) {
  321. endB = a.length;
  322. }
  323. // Create a back-reference for each index
  324. // that should be followed in case of a mismatch.
  325. // We only need B to make these references:
  326. var map = Array(endB);
  327. var k = 0; // Index that lags behind j
  328. map[0] = 0;
  329. for (var j = 1; j < endB; j++) {
  330. if (b[j] == b[k]) {
  331. map[j] = map[k]; // skip over the same character (optional optimisation)
  332. } else {
  333. map[j] = k;
  334. }
  335. while (k > 0 && b[j] != b[k]) {
  336. k = map[k];
  337. }
  338. if (b[j] == b[k]) {
  339. k++;
  340. }
  341. }
  342. // Phase 2: use these references while iterating over A
  343. k = 0;
  344. for (var i = startA; i < a.length; i++) {
  345. while (k > 0 && a[i] != b[k]) {
  346. k = map[k];
  347. }
  348. if (a[i] == b[k]) {
  349. k++;
  350. }
  351. }
  352. return k;
  353. }
  354. /**
  355. * Returns true if the string consistently uses Windows line endings.
  356. */
  357. function hasOnlyWinLineEndings(string) {
  358. return string.includes('\r\n') && !string.startsWith('\n') && !string.match(/[^\r]\n/);
  359. }
  360. /**
  361. * Returns true if the string consistently uses Unix line endings.
  362. */
  363. function hasOnlyUnixLineEndings(string) {
  364. return !string.includes('\r\n') && string.includes('\n');
  365. }
  366. // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode
  367. //
  368. // Ranges and exceptions:
  369. // Latin-1 Supplement, 0080–00FF
  370. // - U+00D7 × Multiplication sign
  371. // - U+00F7 ÷ Division sign
  372. // Latin Extended-A, 0100–017F
  373. // Latin Extended-B, 0180–024F
  374. // IPA Extensions, 0250–02AF
  375. // Spacing Modifier Letters, 02B0–02FF
  376. // - U+02C7 ˇ &#711; Caron
  377. // - U+02D8 ˘ &#728; Breve
  378. // - U+02D9 ˙ &#729; Dot Above
  379. // - U+02DA ˚ &#730; Ring Above
  380. // - U+02DB ˛ &#731; Ogonek
  381. // - U+02DC ˜ &#732; Small Tilde
  382. // - U+02DD ˝ &#733; Double Acute Accent
  383. // Latin Extended Additional, 1E00–1EFF
  384. var extendedWordChars = "a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}";
  385. // Each token is one of the following:
  386. // - A punctuation mark plus the surrounding whitespace
  387. // - A word plus the surrounding whitespace
  388. // - Pure whitespace (but only in the special case where this the entire text
  389. // is just whitespace)
  390. //
  391. // We have to include surrounding whitespace in the tokens because the two
  392. // alternative approaches produce horribly broken results:
  393. // * If we just discard the whitespace, we can't fully reproduce the original
  394. // text from the sequence of tokens and any attempt to render the diff will
  395. // get the whitespace wrong.
  396. // * If we have separate tokens for whitespace, then in a typical text every
  397. // second token will be a single space character. But this often results in
  398. // the optimal diff between two texts being a perverse one that preserves
  399. // the spaces between words but deletes and reinserts actual common words.
  400. // See https://github.com/kpdecker/jsdiff/issues/160#issuecomment-1866099640
  401. // for an example.
  402. //
  403. // Keeping the surrounding whitespace of course has implications for .equals
  404. // and .join, not just .tokenize.
  405. // This regex does NOT fully implement the tokenization rules described above.
  406. // Instead, it gives runs of whitespace their own "token". The tokenize method
  407. // then handles stitching whitespace tokens onto adjacent word or punctuation
  408. // tokens.
  409. var tokenizeIncludingWhitespace = new RegExp("[".concat(extendedWordChars, "]+|\\s+|[^").concat(extendedWordChars, "]"), 'ug');
  410. var wordDiff = new Diff();
  411. wordDiff.equals = function (left, right, options) {
  412. if (options.ignoreCase) {
  413. left = left.toLowerCase();
  414. right = right.toLowerCase();
  415. }
  416. return left.trim() === right.trim();
  417. };
  418. wordDiff.tokenize = function (value) {
  419. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  420. var parts;
  421. if (options.intlSegmenter) {
  422. if (options.intlSegmenter.resolvedOptions().granularity != 'word') {
  423. throw new Error('The segmenter passed must have a granularity of "word"');
  424. }
  425. parts = Array.from(options.intlSegmenter.segment(value), function (segment) {
  426. return segment.segment;
  427. });
  428. } else {
  429. parts = value.match(tokenizeIncludingWhitespace) || [];
  430. }
  431. var tokens = [];
  432. var prevPart = null;
  433. parts.forEach(function (part) {
  434. if (/\s/.test(part)) {
  435. if (prevPart == null) {
  436. tokens.push(part);
  437. } else {
  438. tokens.push(tokens.pop() + part);
  439. }
  440. } else if (/\s/.test(prevPart)) {
  441. if (tokens[tokens.length - 1] == prevPart) {
  442. tokens.push(tokens.pop() + part);
  443. } else {
  444. tokens.push(prevPart + part);
  445. }
  446. } else {
  447. tokens.push(part);
  448. }
  449. prevPart = part;
  450. });
  451. return tokens;
  452. };
  453. wordDiff.join = function (tokens) {
  454. // Tokens being joined here will always have appeared consecutively in the
  455. // same text, so we can simply strip off the leading whitespace from all the
  456. // tokens except the first (and except any whitespace-only tokens - but such
  457. // a token will always be the first and only token anyway) and then join them
  458. // and the whitespace around words and punctuation will end up correct.
  459. return tokens.map(function (token, i) {
  460. if (i == 0) {
  461. return token;
  462. } else {
  463. return token.replace(/^\s+/, '');
  464. }
  465. }).join('');
  466. };
  467. wordDiff.postProcess = function (changes, options) {
  468. if (!changes || options.oneChangePerToken) {
  469. return changes;
  470. }
  471. var lastKeep = null;
  472. // Change objects representing any insertion or deletion since the last
  473. // "keep" change object. There can be at most one of each.
  474. var insertion = null;
  475. var deletion = null;
  476. changes.forEach(function (change) {
  477. if (change.added) {
  478. insertion = change;
  479. } else if (change.removed) {
  480. deletion = change;
  481. } else {
  482. if (insertion || deletion) {
  483. // May be false at start of text
  484. dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change);
  485. }
  486. lastKeep = change;
  487. insertion = null;
  488. deletion = null;
  489. }
  490. });
  491. if (insertion || deletion) {
  492. dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null);
  493. }
  494. return changes;
  495. };
  496. function diffWords(oldStr, newStr, options) {
  497. // This option has never been documented and never will be (it's clearer to
  498. // just call `diffWordsWithSpace` directly if you need that behavior), but
  499. // has existed in jsdiff for a long time, so we retain support for it here
  500. // for the sake of backwards compatibility.
  501. if ((options === null || options === void 0 ? void 0 : options.ignoreWhitespace) != null && !options.ignoreWhitespace) {
  502. return diffWordsWithSpace(oldStr, newStr, options);
  503. }
  504. return wordDiff.diff(oldStr, newStr, options);
  505. }
  506. function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep) {
  507. // Before returning, we tidy up the leading and trailing whitespace of the
  508. // change objects to eliminate cases where trailing whitespace in one object
  509. // is repeated as leading whitespace in the next.
  510. // Below are examples of the outcomes we want here to explain the code.
  511. // I=insert, K=keep, D=delete
  512. // 1. diffing 'foo bar baz' vs 'foo baz'
  513. // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
  514. // After cleanup, we want: K:'foo ' D:'bar ' K:'baz'
  515. //
  516. // 2. Diffing 'foo bar baz' vs 'foo qux baz'
  517. // Prior to cleanup, we have K:'foo ' D:' bar ' I:' qux ' K:' baz'
  518. // After cleanup, we want K:'foo ' D:'bar' I:'qux' K:' baz'
  519. //
  520. // 3. Diffing 'foo\nbar baz' vs 'foo baz'
  521. // Prior to cleanup, we have K:'foo ' D:'\nbar ' K:' baz'
  522. // After cleanup, we want K'foo' D:'\nbar' K:' baz'
  523. //
  524. // 4. Diffing 'foo baz' vs 'foo\nbar baz'
  525. // Prior to cleanup, we have K:'foo\n' I:'\nbar ' K:' baz'
  526. // After cleanup, we ideally want K'foo' I:'\nbar' K:' baz'
  527. // but don't actually manage this currently (the pre-cleanup change
  528. // objects don't contain enough information to make it possible).
  529. //
  530. // 5. Diffing 'foo bar baz' vs 'foo baz'
  531. // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
  532. // After cleanup, we want K:'foo ' D:' bar ' K:'baz'
  533. //
  534. // Our handling is unavoidably imperfect in the case where there's a single
  535. // indel between keeps and the whitespace has changed. For instance, consider
  536. // diffing 'foo\tbar\nbaz' vs 'foo baz'. Unless we create an extra change
  537. // object to represent the insertion of the space character (which isn't even
  538. // a token), we have no way to avoid losing information about the texts'
  539. // original whitespace in the result we return. Still, we do our best to
  540. // output something that will look sensible if we e.g. print it with
  541. // insertions in green and deletions in red.
  542. // Between two "keep" change objects (or before the first or after the last
  543. // change object), we can have either:
  544. // * A "delete" followed by an "insert"
  545. // * Just an "insert"
  546. // * Just a "delete"
  547. // We handle the three cases separately.
  548. if (deletion && insertion) {
  549. var oldWsPrefix = deletion.value.match(/^\s*/)[0];
  550. var oldWsSuffix = deletion.value.match(/\s*$/)[0];
  551. var newWsPrefix = insertion.value.match(/^\s*/)[0];
  552. var newWsSuffix = insertion.value.match(/\s*$/)[0];
  553. if (startKeep) {
  554. var commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
  555. startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
  556. deletion.value = removePrefix(deletion.value, commonWsPrefix);
  557. insertion.value = removePrefix(insertion.value, commonWsPrefix);
  558. }
  559. if (endKeep) {
  560. var commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix);
  561. endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix);
  562. deletion.value = removeSuffix(deletion.value, commonWsSuffix);
  563. insertion.value = removeSuffix(insertion.value, commonWsSuffix);
  564. }
  565. } else if (insertion) {
  566. // The whitespaces all reflect what was in the new text rather than
  567. // the old, so we essentially have no information about whitespace
  568. // insertion or deletion. We just want to dedupe the whitespace.
  569. // We do that by having each change object keep its trailing
  570. // whitespace and deleting duplicate leading whitespace where
  571. // present.
  572. if (startKeep) {
  573. insertion.value = insertion.value.replace(/^\s*/, '');
  574. }
  575. if (endKeep) {
  576. endKeep.value = endKeep.value.replace(/^\s*/, '');
  577. }
  578. // otherwise we've got a deletion and no insertion
  579. } else if (startKeep && endKeep) {
  580. var newWsFull = endKeep.value.match(/^\s*/)[0],
  581. delWsStart = deletion.value.match(/^\s*/)[0],
  582. delWsEnd = deletion.value.match(/\s*$/)[0];
  583. // Any whitespace that comes straight after startKeep in both the old and
  584. // new texts, assign to startKeep and remove from the deletion.
  585. var newWsStart = longestCommonPrefix(newWsFull, delWsStart);
  586. deletion.value = removePrefix(deletion.value, newWsStart);
  587. // Any whitespace that comes straight before endKeep in both the old and
  588. // new texts, and hasn't already been assigned to startKeep, assign to
  589. // endKeep and remove from the deletion.
  590. var newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
  591. deletion.value = removeSuffix(deletion.value, newWsEnd);
  592. endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
  593. // If there's any whitespace from the new text that HASN'T already been
  594. // assigned, assign it to the start:
  595. startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
  596. } else if (endKeep) {
  597. // We are at the start of the text. Preserve all the whitespace on
  598. // endKeep, and just remove whitespace from the end of deletion to the
  599. // extent that it overlaps with the start of endKeep.
  600. var endKeepWsPrefix = endKeep.value.match(/^\s*/)[0];
  601. var deletionWsSuffix = deletion.value.match(/\s*$/)[0];
  602. var overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix);
  603. deletion.value = removeSuffix(deletion.value, overlap);
  604. } else if (startKeep) {
  605. // We are at the END of the text. Preserve all the whitespace on
  606. // startKeep, and just remove whitespace from the start of deletion to
  607. // the extent that it overlaps with the end of startKeep.
  608. var startKeepWsSuffix = startKeep.value.match(/\s*$/)[0];
  609. var deletionWsPrefix = deletion.value.match(/^\s*/)[0];
  610. var _overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix);
  611. deletion.value = removePrefix(deletion.value, _overlap);
  612. }
  613. }
  614. var wordWithSpaceDiff = new Diff();
  615. wordWithSpaceDiff.tokenize = function (value) {
  616. // Slightly different to the tokenizeIncludingWhitespace regex used above in
  617. // that this one treats each individual newline as a distinct tokens, rather
  618. // than merging them into other surrounding whitespace. This was requested
  619. // in https://github.com/kpdecker/jsdiff/issues/180 &
  620. // https://github.com/kpdecker/jsdiff/issues/211
  621. var regex = new RegExp("(\\r?\\n)|[".concat(extendedWordChars, "]+|[^\\S\\n\\r]+|[^").concat(extendedWordChars, "]"), 'ug');
  622. return value.match(regex) || [];
  623. };
  624. function diffWordsWithSpace(oldStr, newStr, options) {
  625. return wordWithSpaceDiff.diff(oldStr, newStr, options);
  626. }
  627. function generateOptions(options, defaults) {
  628. if (typeof options === 'function') {
  629. defaults.callback = options;
  630. } else if (options) {
  631. for (var name in options) {
  632. /* istanbul ignore else */
  633. if (options.hasOwnProperty(name)) {
  634. defaults[name] = options[name];
  635. }
  636. }
  637. }
  638. return defaults;
  639. }
  640. var lineDiff = new Diff();
  641. lineDiff.tokenize = function (value, options) {
  642. if (options.stripTrailingCr) {
  643. // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior
  644. value = value.replace(/\r\n/g, '\n');
  645. }
  646. var retLines = [],
  647. linesAndNewlines = value.split(/(\n|\r\n)/);
  648. // Ignore the final empty token that occurs if the string ends with a new line
  649. if (!linesAndNewlines[linesAndNewlines.length - 1]) {
  650. linesAndNewlines.pop();
  651. }
  652. // Merge the content and line separators into single tokens
  653. for (var i = 0; i < linesAndNewlines.length; i++) {
  654. var line = linesAndNewlines[i];
  655. if (i % 2 && !options.newlineIsToken) {
  656. retLines[retLines.length - 1] += line;
  657. } else {
  658. retLines.push(line);
  659. }
  660. }
  661. return retLines;
  662. };
  663. lineDiff.equals = function (left, right, options) {
  664. // If we're ignoring whitespace, we need to normalise lines by stripping
  665. // whitespace before checking equality. (This has an annoying interaction
  666. // with newlineIsToken that requires special handling: if newlines get their
  667. // own token, then we DON'T want to trim the *newline* tokens down to empty
  668. // strings, since this would cause us to treat whitespace-only line content
  669. // as equal to a separator between lines, which would be weird and
  670. // inconsistent with the documented behavior of the options.)
  671. if (options.ignoreWhitespace) {
  672. if (!options.newlineIsToken || !left.includes('\n')) {
  673. left = left.trim();
  674. }
  675. if (!options.newlineIsToken || !right.includes('\n')) {
  676. right = right.trim();
  677. }
  678. } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
  679. if (left.endsWith('\n')) {
  680. left = left.slice(0, -1);
  681. }
  682. if (right.endsWith('\n')) {
  683. right = right.slice(0, -1);
  684. }
  685. }
  686. return Diff.prototype.equals.call(this, left, right, options);
  687. };
  688. function diffLines(oldStr, newStr, callback) {
  689. return lineDiff.diff(oldStr, newStr, callback);
  690. }
  691. // Kept for backwards compatibility. This is a rather arbitrary wrapper method
  692. // that just calls `diffLines` with `ignoreWhitespace: true`. It's confusing to
  693. // have two ways to do exactly the same thing in the API, so we no longer
  694. // document this one (library users should explicitly use `diffLines` with
  695. // `ignoreWhitespace: true` instead) but we keep it around to maintain
  696. // compatibility with code that used old versions.
  697. function diffTrimmedLines(oldStr, newStr, callback) {
  698. var options = generateOptions(callback, {
  699. ignoreWhitespace: true
  700. });
  701. return lineDiff.diff(oldStr, newStr, options);
  702. }
  703. var sentenceDiff = new Diff();
  704. sentenceDiff.tokenize = function (value) {
  705. return value.split(/(\S.+?[.!?])(?=\s+|$)/);
  706. };
  707. function diffSentences(oldStr, newStr, callback) {
  708. return sentenceDiff.diff(oldStr, newStr, callback);
  709. }
  710. var cssDiff = new Diff();
  711. cssDiff.tokenize = function (value) {
  712. return value.split(/([{}:;,]|\s+)/);
  713. };
  714. function diffCss(oldStr, newStr, callback) {
  715. return cssDiff.diff(oldStr, newStr, callback);
  716. }
  717. function ownKeys(e, r) {
  718. var t = Object.keys(e);
  719. if (Object.getOwnPropertySymbols) {
  720. var o = Object.getOwnPropertySymbols(e);
  721. r && (o = o.filter(function (r) {
  722. return Object.getOwnPropertyDescriptor(e, r).enumerable;
  723. })), t.push.apply(t, o);
  724. }
  725. return t;
  726. }
  727. function _objectSpread2(e) {
  728. for (var r = 1; r < arguments.length; r++) {
  729. var t = null != arguments[r] ? arguments[r] : {};
  730. r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
  731. _defineProperty(e, r, t[r]);
  732. }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
  733. Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
  734. });
  735. }
  736. return e;
  737. }
  738. function _toPrimitive(t, r) {
  739. if ("object" != typeof t || !t) return t;
  740. var e = t[Symbol.toPrimitive];
  741. if (void 0 !== e) {
  742. var i = e.call(t, r || "default");
  743. if ("object" != typeof i) return i;
  744. throw new TypeError("@@toPrimitive must return a primitive value.");
  745. }
  746. return ("string" === r ? String : Number)(t);
  747. }
  748. function _toPropertyKey(t) {
  749. var i = _toPrimitive(t, "string");
  750. return "symbol" == typeof i ? i : i + "";
  751. }
  752. function _typeof(o) {
  753. "@babel/helpers - typeof";
  754. return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
  755. return typeof o;
  756. } : function (o) {
  757. return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
  758. }, _typeof(o);
  759. }
  760. function _defineProperty(obj, key, value) {
  761. key = _toPropertyKey(key);
  762. if (key in obj) {
  763. Object.defineProperty(obj, key, {
  764. value: value,
  765. enumerable: true,
  766. configurable: true,
  767. writable: true
  768. });
  769. } else {
  770. obj[key] = value;
  771. }
  772. return obj;
  773. }
  774. function _toConsumableArray(arr) {
  775. return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
  776. }
  777. function _arrayWithoutHoles(arr) {
  778. if (Array.isArray(arr)) return _arrayLikeToArray(arr);
  779. }
  780. function _iterableToArray(iter) {
  781. if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
  782. }
  783. function _unsupportedIterableToArray(o, minLen) {
  784. if (!o) return;
  785. if (typeof o === "string") return _arrayLikeToArray(o, minLen);
  786. var n = Object.prototype.toString.call(o).slice(8, -1);
  787. if (n === "Object" && o.constructor) n = o.constructor.name;
  788. if (n === "Map" || n === "Set") return Array.from(o);
  789. if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
  790. }
  791. function _arrayLikeToArray(arr, len) {
  792. if (len == null || len > arr.length) len = arr.length;
  793. for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
  794. return arr2;
  795. }
  796. function _nonIterableSpread() {
  797. throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  798. }
  799. var jsonDiff = new Diff();
  800. // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
  801. // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
  802. jsonDiff.useLongestToken = true;
  803. jsonDiff.tokenize = lineDiff.tokenize;
  804. jsonDiff.castInput = function (value, options) {
  805. var undefinedReplacement = options.undefinedReplacement,
  806. _options$stringifyRep = options.stringifyReplacer,
  807. stringifyReplacer = _options$stringifyRep === void 0 ? function (k, v) {
  808. return typeof v === 'undefined' ? undefinedReplacement : v;
  809. } : _options$stringifyRep;
  810. return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' ');
  811. };
  812. jsonDiff.equals = function (left, right, options) {
  813. return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options);
  814. };
  815. function diffJson(oldObj, newObj, options) {
  816. return jsonDiff.diff(oldObj, newObj, options);
  817. }
  818. // This function handles the presence of circular references by bailing out when encountering an
  819. // object that is already on the "stack" of items being processed. Accepts an optional replacer
  820. function canonicalize(obj, stack, replacementStack, replacer, key) {
  821. stack = stack || [];
  822. replacementStack = replacementStack || [];
  823. if (replacer) {
  824. obj = replacer(key, obj);
  825. }
  826. var i;
  827. for (i = 0; i < stack.length; i += 1) {
  828. if (stack[i] === obj) {
  829. return replacementStack[i];
  830. }
  831. }
  832. var canonicalizedObj;
  833. if ('[object Array]' === Object.prototype.toString.call(obj)) {
  834. stack.push(obj);
  835. canonicalizedObj = new Array(obj.length);
  836. replacementStack.push(canonicalizedObj);
  837. for (i = 0; i < obj.length; i += 1) {
  838. canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key);
  839. }
  840. stack.pop();
  841. replacementStack.pop();
  842. return canonicalizedObj;
  843. }
  844. if (obj && obj.toJSON) {
  845. obj = obj.toJSON();
  846. }
  847. if (_typeof(obj) === 'object' && obj !== null) {
  848. stack.push(obj);
  849. canonicalizedObj = {};
  850. replacementStack.push(canonicalizedObj);
  851. var sortedKeys = [],
  852. _key;
  853. for (_key in obj) {
  854. /* istanbul ignore else */
  855. if (Object.prototype.hasOwnProperty.call(obj, _key)) {
  856. sortedKeys.push(_key);
  857. }
  858. }
  859. sortedKeys.sort();
  860. for (i = 0; i < sortedKeys.length; i += 1) {
  861. _key = sortedKeys[i];
  862. canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key);
  863. }
  864. stack.pop();
  865. replacementStack.pop();
  866. } else {
  867. canonicalizedObj = obj;
  868. }
  869. return canonicalizedObj;
  870. }
  871. var arrayDiff = new Diff();
  872. arrayDiff.tokenize = function (value) {
  873. return value.slice();
  874. };
  875. arrayDiff.join = arrayDiff.removeEmpty = function (value) {
  876. return value;
  877. };
  878. function diffArrays(oldArr, newArr, callback) {
  879. return arrayDiff.diff(oldArr, newArr, callback);
  880. }
  881. function unixToWin(patch) {
  882. if (Array.isArray(patch)) {
  883. return patch.map(unixToWin);
  884. }
  885. return _objectSpread2(_objectSpread2({}, patch), {}, {
  886. hunks: patch.hunks.map(function (hunk) {
  887. return _objectSpread2(_objectSpread2({}, hunk), {}, {
  888. lines: hunk.lines.map(function (line, i) {
  889. var _hunk$lines;
  890. return line.startsWith('\\') || line.endsWith('\r') || (_hunk$lines = hunk.lines[i + 1]) !== null && _hunk$lines !== void 0 && _hunk$lines.startsWith('\\') ? line : line + '\r';
  891. })
  892. });
  893. })
  894. });
  895. }
  896. function winToUnix(patch) {
  897. if (Array.isArray(patch)) {
  898. return patch.map(winToUnix);
  899. }
  900. return _objectSpread2(_objectSpread2({}, patch), {}, {
  901. hunks: patch.hunks.map(function (hunk) {
  902. return _objectSpread2(_objectSpread2({}, hunk), {}, {
  903. lines: hunk.lines.map(function (line) {
  904. return line.endsWith('\r') ? line.substring(0, line.length - 1) : line;
  905. })
  906. });
  907. })
  908. });
  909. }
  910. /**
  911. * Returns true if the patch consistently uses Unix line endings (or only involves one line and has
  912. * no line endings).
  913. */
  914. function isUnix(patch) {
  915. if (!Array.isArray(patch)) {
  916. patch = [patch];
  917. }
  918. return !patch.some(function (index) {
  919. return index.hunks.some(function (hunk) {
  920. return hunk.lines.some(function (line) {
  921. return !line.startsWith('\\') && line.endsWith('\r');
  922. });
  923. });
  924. });
  925. }
  926. /**
  927. * Returns true if the patch uses Windows line endings and only Windows line endings.
  928. */
  929. function isWin(patch) {
  930. if (!Array.isArray(patch)) {
  931. patch = [patch];
  932. }
  933. return patch.some(function (index) {
  934. return index.hunks.some(function (hunk) {
  935. return hunk.lines.some(function (line) {
  936. return line.endsWith('\r');
  937. });
  938. });
  939. }) && patch.every(function (index) {
  940. return index.hunks.every(function (hunk) {
  941. return hunk.lines.every(function (line, i) {
  942. var _hunk$lines2;
  943. return line.startsWith('\\') || line.endsWith('\r') || ((_hunk$lines2 = hunk.lines[i + 1]) === null || _hunk$lines2 === void 0 ? void 0 : _hunk$lines2.startsWith('\\'));
  944. });
  945. });
  946. });
  947. }
  948. function parsePatch(uniDiff) {
  949. var diffstr = uniDiff.split(/\n/),
  950. list = [],
  951. i = 0;
  952. function parseIndex() {
  953. var index = {};
  954. list.push(index);
  955. // Parse diff metadata
  956. while (i < diffstr.length) {
  957. var line = diffstr[i];
  958. // File header found, end parsing diff metadata
  959. if (/^(\-\-\-|\+\+\+|@@)\s/.test(line)) {
  960. break;
  961. }
  962. // Diff index
  963. var header = /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec(line);
  964. if (header) {
  965. index.index = header[1];
  966. }
  967. i++;
  968. }
  969. // Parse file headers if they are defined. Unified diff requires them, but
  970. // there's no technical issues to have an isolated hunk without file header
  971. parseFileHeader(index);
  972. parseFileHeader(index);
  973. // Parse hunks
  974. index.hunks = [];
  975. while (i < diffstr.length) {
  976. var _line = diffstr[i];
  977. if (/^(Index:\s|diff\s|\-\-\-\s|\+\+\+\s|===================================================================)/.test(_line)) {
  978. break;
  979. } else if (/^@@/.test(_line)) {
  980. index.hunks.push(parseHunk());
  981. } else if (_line) {
  982. throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(_line));
  983. } else {
  984. i++;
  985. }
  986. }
  987. }
  988. // Parses the --- and +++ headers, if none are found, no lines
  989. // are consumed.
  990. function parseFileHeader(index) {
  991. var fileHeader = /^(---|\+\+\+)\s+(.*)\r?$/.exec(diffstr[i]);
  992. if (fileHeader) {
  993. var keyPrefix = fileHeader[1] === '---' ? 'old' : 'new';
  994. var data = fileHeader[2].split('\t', 2);
  995. var fileName = data[0].replace(/\\\\/g, '\\');
  996. if (/^".*"$/.test(fileName)) {
  997. fileName = fileName.substr(1, fileName.length - 2);
  998. }
  999. index[keyPrefix + 'FileName'] = fileName;
  1000. index[keyPrefix + 'Header'] = (data[1] || '').trim();
  1001. i++;
  1002. }
  1003. }
  1004. // Parses a hunk
  1005. // This assumes that we are at the start of a hunk.
  1006. function parseHunk() {
  1007. var chunkHeaderIndex = i,
  1008. chunkHeaderLine = diffstr[i++],
  1009. chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
  1010. var hunk = {
  1011. oldStart: +chunkHeader[1],
  1012. oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2],
  1013. newStart: +chunkHeader[3],
  1014. newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4],
  1015. lines: []
  1016. };
  1017. // Unified Diff Format quirk: If the chunk size is 0,
  1018. // the first number is one lower than one would expect.
  1019. // https://www.artima.com/weblogs/viewpost.jsp?thread=164293
  1020. if (hunk.oldLines === 0) {
  1021. hunk.oldStart += 1;
  1022. }
  1023. if (hunk.newLines === 0) {
  1024. hunk.newStart += 1;
  1025. }
  1026. var addCount = 0,
  1027. removeCount = 0;
  1028. for (; i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || (_diffstr$i = diffstr[i]) !== null && _diffstr$i !== void 0 && _diffstr$i.startsWith('\\')); i++) {
  1029. var _diffstr$i;
  1030. var operation = diffstr[i].length == 0 && i != diffstr.length - 1 ? ' ' : diffstr[i][0];
  1031. if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
  1032. hunk.lines.push(diffstr[i]);
  1033. if (operation === '+') {
  1034. addCount++;
  1035. } else if (operation === '-') {
  1036. removeCount++;
  1037. } else if (operation === ' ') {
  1038. addCount++;
  1039. removeCount++;
  1040. }
  1041. } else {
  1042. throw new Error("Hunk at line ".concat(chunkHeaderIndex + 1, " contained invalid line ").concat(diffstr[i]));
  1043. }
  1044. }
  1045. // Handle the empty block count case
  1046. if (!addCount && hunk.newLines === 1) {
  1047. hunk.newLines = 0;
  1048. }
  1049. if (!removeCount && hunk.oldLines === 1) {
  1050. hunk.oldLines = 0;
  1051. }
  1052. // Perform sanity checking
  1053. if (addCount !== hunk.newLines) {
  1054. throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
  1055. }
  1056. if (removeCount !== hunk.oldLines) {
  1057. throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
  1058. }
  1059. return hunk;
  1060. }
  1061. while (i < diffstr.length) {
  1062. parseIndex();
  1063. }
  1064. return list;
  1065. }
  1066. // Iterator that traverses in the range of [min, max], stepping
  1067. // by distance from a given start position. I.e. for [0, 4], with
  1068. // start of 2, this will iterate 2, 3, 1, 4, 0.
  1069. function distanceIterator (start, minLine, maxLine) {
  1070. var wantForward = true,
  1071. backwardExhausted = false,
  1072. forwardExhausted = false,
  1073. localOffset = 1;
  1074. return function iterator() {
  1075. if (wantForward && !forwardExhausted) {
  1076. if (backwardExhausted) {
  1077. localOffset++;
  1078. } else {
  1079. wantForward = false;
  1080. }
  1081. // Check if trying to fit beyond text length, and if not, check it fits
  1082. // after offset location (or desired location on first iteration)
  1083. if (start + localOffset <= maxLine) {
  1084. return start + localOffset;
  1085. }
  1086. forwardExhausted = true;
  1087. }
  1088. if (!backwardExhausted) {
  1089. if (!forwardExhausted) {
  1090. wantForward = true;
  1091. }
  1092. // Check if trying to fit before text beginning, and if not, check it fits
  1093. // before offset location
  1094. if (minLine <= start - localOffset) {
  1095. return start - localOffset++;
  1096. }
  1097. backwardExhausted = true;
  1098. return iterator();
  1099. }
  1100. // We tried to fit hunk before text beginning and beyond text length, then
  1101. // hunk can't fit on the text. Return undefined
  1102. };
  1103. }
  1104. function applyPatch(source, uniDiff) {
  1105. var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  1106. if (typeof uniDiff === 'string') {
  1107. uniDiff = parsePatch(uniDiff);
  1108. }
  1109. if (Array.isArray(uniDiff)) {
  1110. if (uniDiff.length > 1) {
  1111. throw new Error('applyPatch only works with a single input.');
  1112. }
  1113. uniDiff = uniDiff[0];
  1114. }
  1115. if (options.autoConvertLineEndings || options.autoConvertLineEndings == null) {
  1116. if (hasOnlyWinLineEndings(source) && isUnix(uniDiff)) {
  1117. uniDiff = unixToWin(uniDiff);
  1118. } else if (hasOnlyUnixLineEndings(source) && isWin(uniDiff)) {
  1119. uniDiff = winToUnix(uniDiff);
  1120. }
  1121. }
  1122. // Apply the diff to the input
  1123. var lines = source.split('\n'),
  1124. hunks = uniDiff.hunks,
  1125. compareLine = options.compareLine || function (lineNumber, line, operation, patchContent) {
  1126. return line === patchContent;
  1127. },
  1128. fuzzFactor = options.fuzzFactor || 0,
  1129. minLine = 0;
  1130. if (fuzzFactor < 0 || !Number.isInteger(fuzzFactor)) {
  1131. throw new Error('fuzzFactor must be a non-negative integer');
  1132. }
  1133. // Special case for empty patch.
  1134. if (!hunks.length) {
  1135. return source;
  1136. }
  1137. // Before anything else, handle EOFNL insertion/removal. If the patch tells us to make a change
  1138. // to the EOFNL that is redundant/impossible - i.e. to remove a newline that's not there, or add a
  1139. // newline that already exists - then we either return false and fail to apply the patch (if
  1140. // fuzzFactor is 0) or simply ignore the problem and do nothing (if fuzzFactor is >0).
  1141. // If we do need to remove/add a newline at EOF, this will always be in the final hunk:
  1142. var prevLine = '',
  1143. removeEOFNL = false,
  1144. addEOFNL = false;
  1145. for (var i = 0; i < hunks[hunks.length - 1].lines.length; i++) {
  1146. var line = hunks[hunks.length - 1].lines[i];
  1147. if (line[0] == '\\') {
  1148. if (prevLine[0] == '+') {
  1149. removeEOFNL = true;
  1150. } else if (prevLine[0] == '-') {
  1151. addEOFNL = true;
  1152. }
  1153. }
  1154. prevLine = line;
  1155. }
  1156. if (removeEOFNL) {
  1157. if (addEOFNL) {
  1158. // This means the final line gets changed but doesn't have a trailing newline in either the
  1159. // original or patched version. In that case, we do nothing if fuzzFactor > 0, and if
  1160. // fuzzFactor is 0, we simply validate that the source file has no trailing newline.
  1161. if (!fuzzFactor && lines[lines.length - 1] == '') {
  1162. return false;
  1163. }
  1164. } else if (lines[lines.length - 1] == '') {
  1165. lines.pop();
  1166. } else if (!fuzzFactor) {
  1167. return false;
  1168. }
  1169. } else if (addEOFNL) {
  1170. if (lines[lines.length - 1] != '') {
  1171. lines.push('');
  1172. } else if (!fuzzFactor) {
  1173. return false;
  1174. }
  1175. }
  1176. /**
  1177. * Checks if the hunk can be made to fit at the provided location with at most `maxErrors`
  1178. * insertions, substitutions, or deletions, while ensuring also that:
  1179. * - lines deleted in the hunk match exactly, and
  1180. * - wherever an insertion operation or block of insertion operations appears in the hunk, the
  1181. * immediately preceding and following lines of context match exactly
  1182. *
  1183. * `toPos` should be set such that lines[toPos] is meant to match hunkLines[0].
  1184. *
  1185. * If the hunk can be applied, returns an object with properties `oldLineLastI` and
  1186. * `replacementLines`. Otherwise, returns null.
  1187. */
  1188. function applyHunk(hunkLines, toPos, maxErrors) {
  1189. var hunkLinesI = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
  1190. var lastContextLineMatched = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
  1191. var patchedLines = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : [];
  1192. var patchedLinesLength = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 0;
  1193. var nConsecutiveOldContextLines = 0;
  1194. var nextContextLineMustMatch = false;
  1195. for (; hunkLinesI < hunkLines.length; hunkLinesI++) {
  1196. var hunkLine = hunkLines[hunkLinesI],
  1197. operation = hunkLine.length > 0 ? hunkLine[0] : ' ',
  1198. content = hunkLine.length > 0 ? hunkLine.substr(1) : hunkLine;
  1199. if (operation === '-') {
  1200. if (compareLine(toPos + 1, lines[toPos], operation, content)) {
  1201. toPos++;
  1202. nConsecutiveOldContextLines = 0;
  1203. } else {
  1204. if (!maxErrors || lines[toPos] == null) {
  1205. return null;
  1206. }
  1207. patchedLines[patchedLinesLength] = lines[toPos];
  1208. return applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1);
  1209. }
  1210. }
  1211. if (operation === '+') {
  1212. if (!lastContextLineMatched) {
  1213. return null;
  1214. }
  1215. patchedLines[patchedLinesLength] = content;
  1216. patchedLinesLength++;
  1217. nConsecutiveOldContextLines = 0;
  1218. nextContextLineMustMatch = true;
  1219. }
  1220. if (operation === ' ') {
  1221. nConsecutiveOldContextLines++;
  1222. patchedLines[patchedLinesLength] = lines[toPos];
  1223. if (compareLine(toPos + 1, lines[toPos], operation, content)) {
  1224. patchedLinesLength++;
  1225. lastContextLineMatched = true;
  1226. nextContextLineMustMatch = false;
  1227. toPos++;
  1228. } else {
  1229. if (nextContextLineMustMatch || !maxErrors) {
  1230. return null;
  1231. }
  1232. // Consider 3 possibilities in sequence:
  1233. // 1. lines contains a *substitution* not included in the patch context, or
  1234. // 2. lines contains an *insertion* not included in the patch context, or
  1235. // 3. lines contains a *deletion* not included in the patch context
  1236. // The first two options are of course only possible if the line from lines is non-null -
  1237. // i.e. only option 3 is possible if we've overrun the end of the old file.
  1238. return lines[toPos] && (applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength + 1) || applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1)) || applyHunk(hunkLines, toPos, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength);
  1239. }
  1240. }
  1241. }
  1242. // Before returning, trim any unmodified context lines off the end of patchedLines and reduce
  1243. // toPos (and thus oldLineLastI) accordingly. This allows later hunks to be applied to a region
  1244. // that starts in this hunk's trailing context.
  1245. patchedLinesLength -= nConsecutiveOldContextLines;
  1246. toPos -= nConsecutiveOldContextLines;
  1247. patchedLines.length = patchedLinesLength;
  1248. return {
  1249. patchedLines: patchedLines,
  1250. oldLineLastI: toPos - 1
  1251. };
  1252. }
  1253. var resultLines = [];
  1254. // Search best fit offsets for each hunk based on the previous ones
  1255. var prevHunkOffset = 0;
  1256. for (var _i = 0; _i < hunks.length; _i++) {
  1257. var hunk = hunks[_i];
  1258. var hunkResult = void 0;
  1259. var maxLine = lines.length - hunk.oldLines + fuzzFactor;
  1260. var toPos = void 0;
  1261. for (var maxErrors = 0; maxErrors <= fuzzFactor; maxErrors++) {
  1262. toPos = hunk.oldStart + prevHunkOffset - 1;
  1263. var iterator = distanceIterator(toPos, minLine, maxLine);
  1264. for (; toPos !== undefined; toPos = iterator()) {
  1265. hunkResult = applyHunk(hunk.lines, toPos, maxErrors);
  1266. if (hunkResult) {
  1267. break;
  1268. }
  1269. }
  1270. if (hunkResult) {
  1271. break;
  1272. }
  1273. }
  1274. if (!hunkResult) {
  1275. return false;
  1276. }
  1277. // Copy everything from the end of where we applied the last hunk to the start of this hunk
  1278. for (var _i2 = minLine; _i2 < toPos; _i2++) {
  1279. resultLines.push(lines[_i2]);
  1280. }
  1281. // Add the lines produced by applying the hunk:
  1282. for (var _i3 = 0; _i3 < hunkResult.patchedLines.length; _i3++) {
  1283. var _line = hunkResult.patchedLines[_i3];
  1284. resultLines.push(_line);
  1285. }
  1286. // Set lower text limit to end of the current hunk, so next ones don't try
  1287. // to fit over already patched text
  1288. minLine = hunkResult.oldLineLastI + 1;
  1289. // Note the offset between where the patch said the hunk should've applied and where we
  1290. // applied it, so we can adjust future hunks accordingly:
  1291. prevHunkOffset = toPos + 1 - hunk.oldStart;
  1292. }
  1293. // Copy over the rest of the lines from the old text
  1294. for (var _i4 = minLine; _i4 < lines.length; _i4++) {
  1295. resultLines.push(lines[_i4]);
  1296. }
  1297. return resultLines.join('\n');
  1298. }
  1299. // Wrapper that supports multiple file patches via callbacks.
  1300. function applyPatches(uniDiff, options) {
  1301. if (typeof uniDiff === 'string') {
  1302. uniDiff = parsePatch(uniDiff);
  1303. }
  1304. var currentIndex = 0;
  1305. function processIndex() {
  1306. var index = uniDiff[currentIndex++];
  1307. if (!index) {
  1308. return options.complete();
  1309. }
  1310. options.loadFile(index, function (err, data) {
  1311. if (err) {
  1312. return options.complete(err);
  1313. }
  1314. var updatedContent = applyPatch(data, index, options);
  1315. options.patched(index, updatedContent, function (err) {
  1316. if (err) {
  1317. return options.complete(err);
  1318. }
  1319. processIndex();
  1320. });
  1321. });
  1322. }
  1323. processIndex();
  1324. }
  1325. function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
  1326. if (!options) {
  1327. options = {};
  1328. }
  1329. if (typeof options === 'function') {
  1330. options = {
  1331. callback: options
  1332. };
  1333. }
  1334. if (typeof options.context === 'undefined') {
  1335. options.context = 4;
  1336. }
  1337. if (options.newlineIsToken) {
  1338. throw new Error('newlineIsToken may not be used with patch-generation functions, only with diffing functions');
  1339. }
  1340. if (!options.callback) {
  1341. return diffLinesResultToPatch(diffLines(oldStr, newStr, options));
  1342. } else {
  1343. var _options = options,
  1344. _callback = _options.callback;
  1345. diffLines(oldStr, newStr, _objectSpread2(_objectSpread2({}, options), {}, {
  1346. callback: function callback(diff) {
  1347. var patch = diffLinesResultToPatch(diff);
  1348. _callback(patch);
  1349. }
  1350. }));
  1351. }
  1352. function diffLinesResultToPatch(diff) {
  1353. // STEP 1: Build up the patch with no "\ No newline at end of file" lines and with the arrays
  1354. // of lines containing trailing newline characters. We'll tidy up later...
  1355. if (!diff) {
  1356. return;
  1357. }
  1358. diff.push({
  1359. value: '',
  1360. lines: []
  1361. }); // Append an empty value to make cleanup easier
  1362. function contextLines(lines) {
  1363. return lines.map(function (entry) {
  1364. return ' ' + entry;
  1365. });
  1366. }
  1367. var hunks = [];
  1368. var oldRangeStart = 0,
  1369. newRangeStart = 0,
  1370. curRange = [],
  1371. oldLine = 1,
  1372. newLine = 1;
  1373. var _loop = function _loop() {
  1374. var current = diff[i],
  1375. lines = current.lines || splitLines(current.value);
  1376. current.lines = lines;
  1377. if (current.added || current.removed) {
  1378. var _curRange;
  1379. // If we have previous context, start with that
  1380. if (!oldRangeStart) {
  1381. var prev = diff[i - 1];
  1382. oldRangeStart = oldLine;
  1383. newRangeStart = newLine;
  1384. if (prev) {
  1385. curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : [];
  1386. oldRangeStart -= curRange.length;
  1387. newRangeStart -= curRange.length;
  1388. }
  1389. }
  1390. // Output our changes
  1391. (_curRange = curRange).push.apply(_curRange, _toConsumableArray(lines.map(function (entry) {
  1392. return (current.added ? '+' : '-') + entry;
  1393. })));
  1394. // Track the updated file position
  1395. if (current.added) {
  1396. newLine += lines.length;
  1397. } else {
  1398. oldLine += lines.length;
  1399. }
  1400. } else {
  1401. // Identical context lines. Track line changes
  1402. if (oldRangeStart) {
  1403. // Close out any changes that have been output (or join overlapping)
  1404. if (lines.length <= options.context * 2 && i < diff.length - 2) {
  1405. var _curRange2;
  1406. // Overlapping
  1407. (_curRange2 = curRange).push.apply(_curRange2, _toConsumableArray(contextLines(lines)));
  1408. } else {
  1409. var _curRange3;
  1410. // end the range and output
  1411. var contextSize = Math.min(lines.length, options.context);
  1412. (_curRange3 = curRange).push.apply(_curRange3, _toConsumableArray(contextLines(lines.slice(0, contextSize))));
  1413. var _hunk = {
  1414. oldStart: oldRangeStart,
  1415. oldLines: oldLine - oldRangeStart + contextSize,
  1416. newStart: newRangeStart,
  1417. newLines: newLine - newRangeStart + contextSize,
  1418. lines: curRange
  1419. };
  1420. hunks.push(_hunk);
  1421. oldRangeStart = 0;
  1422. newRangeStart = 0;
  1423. curRange = [];
  1424. }
  1425. }
  1426. oldLine += lines.length;
  1427. newLine += lines.length;
  1428. }
  1429. };
  1430. for (var i = 0; i < diff.length; i++) {
  1431. _loop();
  1432. }
  1433. // Step 2: eliminate the trailing `\n` from each line of each hunk, and, where needed, add
  1434. // "\ No newline at end of file".
  1435. for (var _i = 0, _hunks = hunks; _i < _hunks.length; _i++) {
  1436. var hunk = _hunks[_i];
  1437. for (var _i2 = 0; _i2 < hunk.lines.length; _i2++) {
  1438. if (hunk.lines[_i2].endsWith('\n')) {
  1439. hunk.lines[_i2] = hunk.lines[_i2].slice(0, -1);
  1440. } else {
  1441. hunk.lines.splice(_i2 + 1, 0, '\\ No newline at end of file');
  1442. _i2++; // Skip the line we just added, then continue iterating
  1443. }
  1444. }
  1445. }
  1446. return {
  1447. oldFileName: oldFileName,
  1448. newFileName: newFileName,
  1449. oldHeader: oldHeader,
  1450. newHeader: newHeader,
  1451. hunks: hunks
  1452. };
  1453. }
  1454. }
  1455. function formatPatch(diff) {
  1456. if (Array.isArray(diff)) {
  1457. return diff.map(formatPatch).join('\n');
  1458. }
  1459. var ret = [];
  1460. if (diff.oldFileName == diff.newFileName) {
  1461. ret.push('Index: ' + diff.oldFileName);
  1462. }
  1463. ret.push('===================================================================');
  1464. ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader));
  1465. ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader));
  1466. for (var i = 0; i < diff.hunks.length; i++) {
  1467. var hunk = diff.hunks[i];
  1468. // Unified Diff Format quirk: If the chunk size is 0,
  1469. // the first number is one lower than one would expect.
  1470. // https://www.artima.com/weblogs/viewpost.jsp?thread=164293
  1471. if (hunk.oldLines === 0) {
  1472. hunk.oldStart -= 1;
  1473. }
  1474. if (hunk.newLines === 0) {
  1475. hunk.newStart -= 1;
  1476. }
  1477. ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@');
  1478. ret.push.apply(ret, hunk.lines);
  1479. }
  1480. return ret.join('\n') + '\n';
  1481. }
  1482. function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
  1483. var _options2;
  1484. if (typeof options === 'function') {
  1485. options = {
  1486. callback: options
  1487. };
  1488. }
  1489. if (!((_options2 = options) !== null && _options2 !== void 0 && _options2.callback)) {
  1490. var patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options);
  1491. if (!patchObj) {
  1492. return;
  1493. }
  1494. return formatPatch(patchObj);
  1495. } else {
  1496. var _options3 = options,
  1497. _callback2 = _options3.callback;
  1498. structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, _objectSpread2(_objectSpread2({}, options), {}, {
  1499. callback: function callback(patchObj) {
  1500. if (!patchObj) {
  1501. _callback2();
  1502. } else {
  1503. _callback2(formatPatch(patchObj));
  1504. }
  1505. }
  1506. }));
  1507. }
  1508. }
  1509. function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) {
  1510. return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options);
  1511. }
  1512. /**
  1513. * Split `text` into an array of lines, including the trailing newline character (where present)
  1514. */
  1515. function splitLines(text) {
  1516. var hasTrailingNl = text.endsWith('\n');
  1517. var result = text.split('\n').map(function (line) {
  1518. return line + '\n';
  1519. });
  1520. if (hasTrailingNl) {
  1521. result.pop();
  1522. } else {
  1523. result.push(result.pop().slice(0, -1));
  1524. }
  1525. return result;
  1526. }
  1527. function arrayEqual(a, b) {
  1528. if (a.length !== b.length) {
  1529. return false;
  1530. }
  1531. return arrayStartsWith(a, b);
  1532. }
  1533. function arrayStartsWith(array, start) {
  1534. if (start.length > array.length) {
  1535. return false;
  1536. }
  1537. for (var i = 0; i < start.length; i++) {
  1538. if (start[i] !== array[i]) {
  1539. return false;
  1540. }
  1541. }
  1542. return true;
  1543. }
  1544. function calcLineCount(hunk) {
  1545. var _calcOldNewLineCount = calcOldNewLineCount(hunk.lines),
  1546. oldLines = _calcOldNewLineCount.oldLines,
  1547. newLines = _calcOldNewLineCount.newLines;
  1548. if (oldLines !== undefined) {
  1549. hunk.oldLines = oldLines;
  1550. } else {
  1551. delete hunk.oldLines;
  1552. }
  1553. if (newLines !== undefined) {
  1554. hunk.newLines = newLines;
  1555. } else {
  1556. delete hunk.newLines;
  1557. }
  1558. }
  1559. function merge(mine, theirs, base) {
  1560. mine = loadPatch(mine, base);
  1561. theirs = loadPatch(theirs, base);
  1562. var ret = {};
  1563. // For index we just let it pass through as it doesn't have any necessary meaning.
  1564. // Leaving sanity checks on this to the API consumer that may know more about the
  1565. // meaning in their own context.
  1566. if (mine.index || theirs.index) {
  1567. ret.index = mine.index || theirs.index;
  1568. }
  1569. if (mine.newFileName || theirs.newFileName) {
  1570. if (!fileNameChanged(mine)) {
  1571. // No header or no change in ours, use theirs (and ours if theirs does not exist)
  1572. ret.oldFileName = theirs.oldFileName || mine.oldFileName;
  1573. ret.newFileName = theirs.newFileName || mine.newFileName;
  1574. ret.oldHeader = theirs.oldHeader || mine.oldHeader;
  1575. ret.newHeader = theirs.newHeader || mine.newHeader;
  1576. } else if (!fileNameChanged(theirs)) {
  1577. // No header or no change in theirs, use ours
  1578. ret.oldFileName = mine.oldFileName;
  1579. ret.newFileName = mine.newFileName;
  1580. ret.oldHeader = mine.oldHeader;
  1581. ret.newHeader = mine.newHeader;
  1582. } else {
  1583. // Both changed... figure it out
  1584. ret.oldFileName = selectField(ret, mine.oldFileName, theirs.oldFileName);
  1585. ret.newFileName = selectField(ret, mine.newFileName, theirs.newFileName);
  1586. ret.oldHeader = selectField(ret, mine.oldHeader, theirs.oldHeader);
  1587. ret.newHeader = selectField(ret, mine.newHeader, theirs.newHeader);
  1588. }
  1589. }
  1590. ret.hunks = [];
  1591. var mineIndex = 0,
  1592. theirsIndex = 0,
  1593. mineOffset = 0,
  1594. theirsOffset = 0;
  1595. while (mineIndex < mine.hunks.length || theirsIndex < theirs.hunks.length) {
  1596. var mineCurrent = mine.hunks[mineIndex] || {
  1597. oldStart: Infinity
  1598. },
  1599. theirsCurrent = theirs.hunks[theirsIndex] || {
  1600. oldStart: Infinity
  1601. };
  1602. if (hunkBefore(mineCurrent, theirsCurrent)) {
  1603. // This patch does not overlap with any of the others, yay.
  1604. ret.hunks.push(cloneHunk(mineCurrent, mineOffset));
  1605. mineIndex++;
  1606. theirsOffset += mineCurrent.newLines - mineCurrent.oldLines;
  1607. } else if (hunkBefore(theirsCurrent, mineCurrent)) {
  1608. // This patch does not overlap with any of the others, yay.
  1609. ret.hunks.push(cloneHunk(theirsCurrent, theirsOffset));
  1610. theirsIndex++;
  1611. mineOffset += theirsCurrent.newLines - theirsCurrent.oldLines;
  1612. } else {
  1613. // Overlap, merge as best we can
  1614. var mergedHunk = {
  1615. oldStart: Math.min(mineCurrent.oldStart, theirsCurrent.oldStart),
  1616. oldLines: 0,
  1617. newStart: Math.min(mineCurrent.newStart + mineOffset, theirsCurrent.oldStart + theirsOffset),
  1618. newLines: 0,
  1619. lines: []
  1620. };
  1621. mergeLines(mergedHunk, mineCurrent.oldStart, mineCurrent.lines, theirsCurrent.oldStart, theirsCurrent.lines);
  1622. theirsIndex++;
  1623. mineIndex++;
  1624. ret.hunks.push(mergedHunk);
  1625. }
  1626. }
  1627. return ret;
  1628. }
  1629. function loadPatch(param, base) {
  1630. if (typeof param === 'string') {
  1631. if (/^@@/m.test(param) || /^Index:/m.test(param)) {
  1632. return parsePatch(param)[0];
  1633. }
  1634. if (!base) {
  1635. throw new Error('Must provide a base reference or pass in a patch');
  1636. }
  1637. return structuredPatch(undefined, undefined, base, param);
  1638. }
  1639. return param;
  1640. }
  1641. function fileNameChanged(patch) {
  1642. return patch.newFileName && patch.newFileName !== patch.oldFileName;
  1643. }
  1644. function selectField(index, mine, theirs) {
  1645. if (mine === theirs) {
  1646. return mine;
  1647. } else {
  1648. index.conflict = true;
  1649. return {
  1650. mine: mine,
  1651. theirs: theirs
  1652. };
  1653. }
  1654. }
  1655. function hunkBefore(test, check) {
  1656. return test.oldStart < check.oldStart && test.oldStart + test.oldLines < check.oldStart;
  1657. }
  1658. function cloneHunk(hunk, offset) {
  1659. return {
  1660. oldStart: hunk.oldStart,
  1661. oldLines: hunk.oldLines,
  1662. newStart: hunk.newStart + offset,
  1663. newLines: hunk.newLines,
  1664. lines: hunk.lines
  1665. };
  1666. }
  1667. function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) {
  1668. // This will generally result in a conflicted hunk, but there are cases where the context
  1669. // is the only overlap where we can successfully merge the content here.
  1670. var mine = {
  1671. offset: mineOffset,
  1672. lines: mineLines,
  1673. index: 0
  1674. },
  1675. their = {
  1676. offset: theirOffset,
  1677. lines: theirLines,
  1678. index: 0
  1679. };
  1680. // Handle any leading content
  1681. insertLeading(hunk, mine, their);
  1682. insertLeading(hunk, their, mine);
  1683. // Now in the overlap content. Scan through and select the best changes from each.
  1684. while (mine.index < mine.lines.length && their.index < their.lines.length) {
  1685. var mineCurrent = mine.lines[mine.index],
  1686. theirCurrent = their.lines[their.index];
  1687. if ((mineCurrent[0] === '-' || mineCurrent[0] === '+') && (theirCurrent[0] === '-' || theirCurrent[0] === '+')) {
  1688. // Both modified ...
  1689. mutualChange(hunk, mine, their);
  1690. } else if (mineCurrent[0] === '+' && theirCurrent[0] === ' ') {
  1691. var _hunk$lines;
  1692. // Mine inserted
  1693. (_hunk$lines = hunk.lines).push.apply(_hunk$lines, _toConsumableArray(collectChange(mine)));
  1694. } else if (theirCurrent[0] === '+' && mineCurrent[0] === ' ') {
  1695. var _hunk$lines2;
  1696. // Theirs inserted
  1697. (_hunk$lines2 = hunk.lines).push.apply(_hunk$lines2, _toConsumableArray(collectChange(their)));
  1698. } else if (mineCurrent[0] === '-' && theirCurrent[0] === ' ') {
  1699. // Mine removed or edited
  1700. removal(hunk, mine, their);
  1701. } else if (theirCurrent[0] === '-' && mineCurrent[0] === ' ') {
  1702. // Their removed or edited
  1703. removal(hunk, their, mine, true);
  1704. } else if (mineCurrent === theirCurrent) {
  1705. // Context identity
  1706. hunk.lines.push(mineCurrent);
  1707. mine.index++;
  1708. their.index++;
  1709. } else {
  1710. // Context mismatch
  1711. conflict(hunk, collectChange(mine), collectChange(their));
  1712. }
  1713. }
  1714. // Now push anything that may be remaining
  1715. insertTrailing(hunk, mine);
  1716. insertTrailing(hunk, their);
  1717. calcLineCount(hunk);
  1718. }
  1719. function mutualChange(hunk, mine, their) {
  1720. var myChanges = collectChange(mine),
  1721. theirChanges = collectChange(their);
  1722. if (allRemoves(myChanges) && allRemoves(theirChanges)) {
  1723. // Special case for remove changes that are supersets of one another
  1724. if (arrayStartsWith(myChanges, theirChanges) && skipRemoveSuperset(their, myChanges, myChanges.length - theirChanges.length)) {
  1725. var _hunk$lines3;
  1726. (_hunk$lines3 = hunk.lines).push.apply(_hunk$lines3, _toConsumableArray(myChanges));
  1727. return;
  1728. } else if (arrayStartsWith(theirChanges, myChanges) && skipRemoveSuperset(mine, theirChanges, theirChanges.length - myChanges.length)) {
  1729. var _hunk$lines4;
  1730. (_hunk$lines4 = hunk.lines).push.apply(_hunk$lines4, _toConsumableArray(theirChanges));
  1731. return;
  1732. }
  1733. } else if (arrayEqual(myChanges, theirChanges)) {
  1734. var _hunk$lines5;
  1735. (_hunk$lines5 = hunk.lines).push.apply(_hunk$lines5, _toConsumableArray(myChanges));
  1736. return;
  1737. }
  1738. conflict(hunk, myChanges, theirChanges);
  1739. }
  1740. function removal(hunk, mine, their, swap) {
  1741. var myChanges = collectChange(mine),
  1742. theirChanges = collectContext(their, myChanges);
  1743. if (theirChanges.merged) {
  1744. var _hunk$lines6;
  1745. (_hunk$lines6 = hunk.lines).push.apply(_hunk$lines6, _toConsumableArray(theirChanges.merged));
  1746. } else {
  1747. conflict(hunk, swap ? theirChanges : myChanges, swap ? myChanges : theirChanges);
  1748. }
  1749. }
  1750. function conflict(hunk, mine, their) {
  1751. hunk.conflict = true;
  1752. hunk.lines.push({
  1753. conflict: true,
  1754. mine: mine,
  1755. theirs: their
  1756. });
  1757. }
  1758. function insertLeading(hunk, insert, their) {
  1759. while (insert.offset < their.offset && insert.index < insert.lines.length) {
  1760. var line = insert.lines[insert.index++];
  1761. hunk.lines.push(line);
  1762. insert.offset++;
  1763. }
  1764. }
  1765. function insertTrailing(hunk, insert) {
  1766. while (insert.index < insert.lines.length) {
  1767. var line = insert.lines[insert.index++];
  1768. hunk.lines.push(line);
  1769. }
  1770. }
  1771. function collectChange(state) {
  1772. var ret = [],
  1773. operation = state.lines[state.index][0];
  1774. while (state.index < state.lines.length) {
  1775. var line = state.lines[state.index];
  1776. // Group additions that are immediately after subtractions and treat them as one "atomic" modify change.
  1777. if (operation === '-' && line[0] === '+') {
  1778. operation = '+';
  1779. }
  1780. if (operation === line[0]) {
  1781. ret.push(line);
  1782. state.index++;
  1783. } else {
  1784. break;
  1785. }
  1786. }
  1787. return ret;
  1788. }
  1789. function collectContext(state, matchChanges) {
  1790. var changes = [],
  1791. merged = [],
  1792. matchIndex = 0,
  1793. contextChanges = false,
  1794. conflicted = false;
  1795. while (matchIndex < matchChanges.length && state.index < state.lines.length) {
  1796. var change = state.lines[state.index],
  1797. match = matchChanges[matchIndex];
  1798. // Once we've hit our add, then we are done
  1799. if (match[0] === '+') {
  1800. break;
  1801. }
  1802. contextChanges = contextChanges || change[0] !== ' ';
  1803. merged.push(match);
  1804. matchIndex++;
  1805. // Consume any additions in the other block as a conflict to attempt
  1806. // to pull in the remaining context after this
  1807. if (change[0] === '+') {
  1808. conflicted = true;
  1809. while (change[0] === '+') {
  1810. changes.push(change);
  1811. change = state.lines[++state.index];
  1812. }
  1813. }
  1814. if (match.substr(1) === change.substr(1)) {
  1815. changes.push(change);
  1816. state.index++;
  1817. } else {
  1818. conflicted = true;
  1819. }
  1820. }
  1821. if ((matchChanges[matchIndex] || '')[0] === '+' && contextChanges) {
  1822. conflicted = true;
  1823. }
  1824. if (conflicted) {
  1825. return changes;
  1826. }
  1827. while (matchIndex < matchChanges.length) {
  1828. merged.push(matchChanges[matchIndex++]);
  1829. }
  1830. return {
  1831. merged: merged,
  1832. changes: changes
  1833. };
  1834. }
  1835. function allRemoves(changes) {
  1836. return changes.reduce(function (prev, change) {
  1837. return prev && change[0] === '-';
  1838. }, true);
  1839. }
  1840. function skipRemoveSuperset(state, removeChanges, delta) {
  1841. for (var i = 0; i < delta; i++) {
  1842. var changeContent = removeChanges[removeChanges.length - delta + i].substr(1);
  1843. if (state.lines[state.index + i] !== ' ' + changeContent) {
  1844. return false;
  1845. }
  1846. }
  1847. state.index += delta;
  1848. return true;
  1849. }
  1850. function calcOldNewLineCount(lines) {
  1851. var oldLines = 0;
  1852. var newLines = 0;
  1853. lines.forEach(function (line) {
  1854. if (typeof line !== 'string') {
  1855. var myCount = calcOldNewLineCount(line.mine);
  1856. var theirCount = calcOldNewLineCount(line.theirs);
  1857. if (oldLines !== undefined) {
  1858. if (myCount.oldLines === theirCount.oldLines) {
  1859. oldLines += myCount.oldLines;
  1860. } else {
  1861. oldLines = undefined;
  1862. }
  1863. }
  1864. if (newLines !== undefined) {
  1865. if (myCount.newLines === theirCount.newLines) {
  1866. newLines += myCount.newLines;
  1867. } else {
  1868. newLines = undefined;
  1869. }
  1870. }
  1871. } else {
  1872. if (newLines !== undefined && (line[0] === '+' || line[0] === ' ')) {
  1873. newLines++;
  1874. }
  1875. if (oldLines !== undefined && (line[0] === '-' || line[0] === ' ')) {
  1876. oldLines++;
  1877. }
  1878. }
  1879. });
  1880. return {
  1881. oldLines: oldLines,
  1882. newLines: newLines
  1883. };
  1884. }
  1885. function reversePatch(structuredPatch) {
  1886. if (Array.isArray(structuredPatch)) {
  1887. return structuredPatch.map(reversePatch).reverse();
  1888. }
  1889. return _objectSpread2(_objectSpread2({}, structuredPatch), {}, {
  1890. oldFileName: structuredPatch.newFileName,
  1891. oldHeader: structuredPatch.newHeader,
  1892. newFileName: structuredPatch.oldFileName,
  1893. newHeader: structuredPatch.oldHeader,
  1894. hunks: structuredPatch.hunks.map(function (hunk) {
  1895. return {
  1896. oldLines: hunk.newLines,
  1897. oldStart: hunk.newStart,
  1898. newLines: hunk.oldLines,
  1899. newStart: hunk.oldStart,
  1900. lines: hunk.lines.map(function (l) {
  1901. if (l.startsWith('-')) {
  1902. return "+".concat(l.slice(1));
  1903. }
  1904. if (l.startsWith('+')) {
  1905. return "-".concat(l.slice(1));
  1906. }
  1907. return l;
  1908. })
  1909. };
  1910. })
  1911. });
  1912. }
  1913. // See: http://code.google.com/p/google-diff-match-patch/wiki/API
  1914. function convertChangesToDMP(changes) {
  1915. var ret = [],
  1916. change,
  1917. operation;
  1918. for (var i = 0; i < changes.length; i++) {
  1919. change = changes[i];
  1920. if (change.added) {
  1921. operation = 1;
  1922. } else if (change.removed) {
  1923. operation = -1;
  1924. } else {
  1925. operation = 0;
  1926. }
  1927. ret.push([operation, change.value]);
  1928. }
  1929. return ret;
  1930. }
  1931. function convertChangesToXML(changes) {
  1932. var ret = [];
  1933. for (var i = 0; i < changes.length; i++) {
  1934. var change = changes[i];
  1935. if (change.added) {
  1936. ret.push('<ins>');
  1937. } else if (change.removed) {
  1938. ret.push('<del>');
  1939. }
  1940. ret.push(escapeHTML(change.value));
  1941. if (change.added) {
  1942. ret.push('</ins>');
  1943. } else if (change.removed) {
  1944. ret.push('</del>');
  1945. }
  1946. }
  1947. return ret.join('');
  1948. }
  1949. function escapeHTML(s) {
  1950. var n = s;
  1951. n = n.replace(/&/g, '&amp;');
  1952. n = n.replace(/</g, '&lt;');
  1953. n = n.replace(/>/g, '&gt;');
  1954. n = n.replace(/"/g, '&quot;');
  1955. return n;
  1956. }
  1957. export { Diff, applyPatch, applyPatches, canonicalize, convertChangesToDMP, convertChangesToXML, createPatch, createTwoFilesPatch, diffArrays, diffChars, diffCss, diffJson, diffLines, diffSentences, diffTrimmedLines, diffWords, diffWordsWithSpace, formatPatch, merge, parsePatch, reversePatch, structuredPatch };