Comparing version 1.1.2 to 1.2.0
506
diff.js
@@ -42,8 +42,8 @@ /** | ||
* @param {string} text2 New string to be diffed. | ||
* @param {Int} cursor_pos Expected edit position in text1 (optional) | ||
* @param {Int|Object} [cursor_pos] Edit position in text1 or object with more info | ||
* @return {Array} Array of diff tuples. | ||
*/ | ||
function diff_main(text1, text2, cursor_pos) { | ||
// Check for equality (speedup). | ||
if (text1 == text2) { | ||
function diff_main(text1, text2, cursor_pos, _fix_unicode) { | ||
// Check for equality | ||
if (text1 === text2) { | ||
if (text1) { | ||
@@ -55,5 +55,7 @@ return [[DIFF_EQUAL, text1]]; | ||
// Check cursor_pos within bounds | ||
if (cursor_pos < 0 || text1.length < cursor_pos) { | ||
cursor_pos = null; | ||
if (cursor_pos != null) { | ||
var editdiff = find_cursor_edit_diff(text1, text2, cursor_pos); | ||
if (editdiff) { | ||
return editdiff; | ||
} | ||
} | ||
@@ -83,7 +85,3 @@ | ||
} | ||
diff_cleanupMerge(diffs); | ||
if (cursor_pos != null) { | ||
diffs = fix_cursor(diffs, cursor_pos); | ||
} | ||
diffs = fix_emoji(diffs); | ||
diff_cleanupMerge(diffs, _fix_unicode); | ||
return diffs; | ||
@@ -116,7 +114,9 @@ }; | ||
var i = longtext.indexOf(shorttext); | ||
if (i != -1) { | ||
if (i !== -1) { | ||
// Shorter text is inside the longer text (speedup). | ||
diffs = [[DIFF_INSERT, longtext.substring(0, i)], | ||
[DIFF_EQUAL, shorttext], | ||
[DIFF_INSERT, longtext.substring(i + shorttext.length)]]; | ||
diffs = [ | ||
[DIFF_INSERT, longtext.substring(0, i)], | ||
[DIFF_EQUAL, shorttext], | ||
[DIFF_INSERT, longtext.substring(i + shorttext.length)] | ||
]; | ||
// Swap insertions for deletions if diff is reversed. | ||
@@ -129,3 +129,3 @@ if (text1.length > text2.length) { | ||
if (shorttext.length == 1) { | ||
if (shorttext.length === 1) { | ||
// Single character string. | ||
@@ -185,3 +185,3 @@ // After the previous speedup, the character can't be an equality. | ||
// with the reverse path. | ||
var front = (delta % 2 != 0); | ||
var front = (delta % 2 !== 0); | ||
// Offsets for start and end of k loop. | ||
@@ -198,3 +198,3 @@ // Prevents mapping of space beyond the grid. | ||
var x1; | ||
if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { | ||
if (k1 === -d || (k1 !== d && v1[k1_offset - 1] < v1[k1_offset + 1])) { | ||
x1 = v1[k1_offset + 1]; | ||
@@ -205,4 +205,6 @@ } else { | ||
var y1 = x1 - k1; | ||
while (x1 < text1_length && y1 < text2_length && | ||
text1.charAt(x1) == text2.charAt(y1)) { | ||
while ( | ||
x1 < text1_length && y1 < text2_length && | ||
text1.charAt(x1) === text2.charAt(y1) | ||
) { | ||
x1++; | ||
@@ -220,3 +222,3 @@ y1++; | ||
var k2_offset = v_offset + delta - k1; | ||
if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { | ||
if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] !== -1) { | ||
// Mirror x2 onto top-left coordinate system. | ||
@@ -236,3 +238,3 @@ var x2 = text1_length - v2[k2_offset]; | ||
var x2; | ||
if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { | ||
if (k2 === -d || (k2 !== d && v2[k2_offset - 1] < v2[k2_offset + 1])) { | ||
x2 = v2[k2_offset + 1]; | ||
@@ -243,5 +245,6 @@ } else { | ||
var y2 = x2 - k2; | ||
while (x2 < text1_length && y2 < text2_length && | ||
text1.charAt(text1_length - x2 - 1) == | ||
text2.charAt(text2_length - y2 - 1)) { | ||
while ( | ||
x2 < text1_length && y2 < text2_length && | ||
text1.charAt(text1_length - x2 - 1) === text2.charAt(text2_length - y2 - 1) | ||
) { | ||
x2++; | ||
@@ -259,3 +262,3 @@ y2++; | ||
var k1_offset = v_offset + delta - k2; | ||
if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { | ||
if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] !== -1) { | ||
var x1 = v1[k1_offset]; | ||
@@ -311,3 +314,3 @@ var y1 = v_offset + x1 - k1_offset; | ||
// Quick check for common null cases. | ||
if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) { | ||
if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) { | ||
return 0; | ||
@@ -322,4 +325,6 @@ } | ||
while (pointermin < pointermid) { | ||
if (text1.substring(pointerstart, pointermid) == | ||
text2.substring(pointerstart, pointermid)) { | ||
if ( | ||
text1.substring(pointerstart, pointermid) == | ||
text2.substring(pointerstart, pointermid) | ||
) { | ||
pointermin = pointermid; | ||
@@ -332,2 +337,7 @@ pointerstart = pointermin; | ||
} | ||
if (is_surrogate_pair_start(text1.charCodeAt(pointermid - 1))) { | ||
pointermid--; | ||
} | ||
return pointermid; | ||
@@ -345,4 +355,3 @@ }; | ||
// Quick check for common null cases. | ||
if (!text1 || !text2 || | ||
text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) { | ||
if (!text1 || !text2 || text1.slice(-1) !== text2.slice(-1)) { | ||
return 0; | ||
@@ -357,4 +366,6 @@ } | ||
while (pointermin < pointermid) { | ||
if (text1.substring(text1.length - pointermid, text1.length - pointerend) == | ||
text2.substring(text2.length - pointermid, text2.length - pointerend)) { | ||
if ( | ||
text1.substring(text1.length - pointermid, text1.length - pointerend) == | ||
text2.substring(text2.length - pointermid, text2.length - pointerend) | ||
) { | ||
pointermin = pointermid; | ||
@@ -367,2 +378,7 @@ pointerend = pointermin; | ||
} | ||
if (is_surrogate_pair_end(text1.charCodeAt(text1.length - pointermid))) { | ||
pointermid--; | ||
} | ||
return pointermid; | ||
@@ -407,10 +423,10 @@ }; | ||
var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b; | ||
while ((j = shorttext.indexOf(seed, j + 1)) != -1) { | ||
var prefixLength = diff_commonPrefix(longtext.substring(i), | ||
shorttext.substring(j)); | ||
var suffixLength = diff_commonSuffix(longtext.substring(0, i), | ||
shorttext.substring(0, j)); | ||
while ((j = shorttext.indexOf(seed, j + 1)) !== -1) { | ||
var prefixLength = diff_commonPrefix( | ||
longtext.substring(i), shorttext.substring(j)); | ||
var suffixLength = diff_commonSuffix( | ||
longtext.substring(0, i), shorttext.substring(0, j)); | ||
if (best_common.length < suffixLength + prefixLength) { | ||
best_common = shorttext.substring(j - suffixLength, j) + | ||
shorttext.substring(j, j + prefixLength); | ||
best_common = shorttext.substring( | ||
j - suffixLength, j) + shorttext.substring(j, j + prefixLength); | ||
best_longtext_a = longtext.substring(0, i - suffixLength); | ||
@@ -423,4 +439,6 @@ best_longtext_b = longtext.substring(i + prefixLength); | ||
if (best_common.length * 2 >= longtext.length) { | ||
return [best_longtext_a, best_longtext_b, | ||
best_shorttext_a, best_shorttext_b, best_common]; | ||
return [ | ||
best_longtext_a, best_longtext_b, | ||
best_shorttext_a, best_shorttext_b, best_common | ||
]; | ||
} else { | ||
@@ -432,7 +450,5 @@ return null; | ||
// First check if the second quarter is the seed for a half-match. | ||
var hm1 = diff_halfMatchI_(longtext, shorttext, | ||
Math.ceil(longtext.length / 4)); | ||
var hm1 = diff_halfMatchI_(longtext, shorttext, Math.ceil(longtext.length / 4)); | ||
// Check again based on the third quarter. | ||
var hm2 = diff_halfMatchI_(longtext, shorttext, | ||
Math.ceil(longtext.length / 2)); | ||
var hm2 = diff_halfMatchI_(longtext, shorttext, Math.ceil(longtext.length / 2)); | ||
var hm; | ||
@@ -472,4 +488,5 @@ if (!hm1 && !hm2) { | ||
* @param {Array} diffs Array of diff tuples. | ||
* @param {boolean} fix_unicode Whether to normalize to a unicode-correct diff | ||
*/ | ||
function diff_cleanupMerge(diffs) { | ||
function diff_cleanupMerge(diffs, fix_unicode) { | ||
diffs.push([DIFF_EQUAL, '']); // Add a dummy entry at the end. | ||
@@ -483,4 +500,9 @@ var pointer = 0; | ||
while (pointer < diffs.length) { | ||
if (pointer < diffs.length - 1 && !diffs[pointer][1]) { | ||
diffs.splice(pointer, 1); | ||
continue; | ||
} | ||
switch (diffs[pointer][0]) { | ||
case DIFF_INSERT: | ||
count_insert++; | ||
@@ -496,16 +518,59 @@ text_insert += diffs[pointer][1]; | ||
case DIFF_EQUAL: | ||
// Upon reaching an equality, check for prior redundancies. | ||
if (count_delete + count_insert > 1) { | ||
if (count_delete !== 0 && count_insert !== 0) { | ||
// Factor out any common prefixies. | ||
var previous_equality = pointer - count_insert - count_delete - 1; | ||
if (fix_unicode) { | ||
// prevent splitting of unicode surrogate pairs. when fix_unicode is true, | ||
// we assume that the old and new text in the diff are complete and correct | ||
// unicode-encoded JS strings, but the tuple boundaries may fall between | ||
// surrogate pairs. we fix this by shaving off stray surrogates from the end | ||
// of the previous equality and the beginning of this equality. this may create | ||
// empty equalities or a common prefix or suffix. for example, if AB and AC are | ||
// emojis, `[[0, 'A'], [-1, 'BA'], [0, 'C']]` would turn into deleting 'ABAC' and | ||
// inserting 'AC', and then the common suffix 'AC' will be eliminated. in this | ||
// particular case, both equalities go away, we absorb any previous inequalities, | ||
// and we keep scanning for the next equality before rewriting the tuples. | ||
if (previous_equality >= 0 && ends_with_pair_start(diffs[previous_equality][1])) { | ||
var stray = diffs[previous_equality][1].slice(-1); | ||
diffs[previous_equality][1] = diffs[previous_equality][1].slice(0, -1); | ||
text_delete = stray + text_delete; | ||
text_insert = stray + text_insert; | ||
if (!diffs[previous_equality][1]) { | ||
// emptied out previous equality, so delete it and include previous delete/insert | ||
diffs.splice(previous_equality, 1); | ||
pointer--; | ||
var k = previous_equality - 1; | ||
if (diffs[k] && diffs[k][0] === DIFF_INSERT) { | ||
count_insert++; | ||
text_insert = diffs[k][1] + text_insert; | ||
k--; | ||
} | ||
if (diffs[k] && diffs[k][0] === DIFF_DELETE) { | ||
count_delete++; | ||
text_delete = diffs[k][1] + text_delete; | ||
k--; | ||
} | ||
previous_equality = k; | ||
} | ||
} | ||
if (starts_with_pair_end(diffs[pointer][1])) { | ||
var stray = diffs[pointer][1].charAt(0); | ||
diffs[pointer][1] = diffs[pointer][1].slice(1); | ||
text_delete += stray; | ||
text_insert += stray; | ||
} | ||
} | ||
if (pointer < diffs.length - 1 && !diffs[pointer][1]) { | ||
// for empty equality not at end, wait for next equality | ||
diffs.splice(pointer, 1); | ||
break; | ||
} | ||
if (text_delete.length > 0 || text_insert.length > 0) { | ||
// note that diff_commonPrefix and diff_commonSuffix are unicode-aware | ||
if (text_delete.length > 0 && text_insert.length > 0) { | ||
// Factor out any common prefixes. | ||
commonlength = diff_commonPrefix(text_insert, text_delete); | ||
if (commonlength !== 0) { | ||
if ((pointer - count_delete - count_insert) > 0 && | ||
diffs[pointer - count_delete - count_insert - 1][0] == | ||
DIFF_EQUAL) { | ||
diffs[pointer - count_delete - count_insert - 1][1] += | ||
text_insert.substring(0, commonlength); | ||
if (previous_equality >= 0) { | ||
diffs[previous_equality][1] += text_insert.substring(0, commonlength); | ||
} else { | ||
diffs.splice(0, 0, [DIFF_EQUAL, | ||
text_insert.substring(0, commonlength)]); | ||
diffs.splice(0, 0, [DIFF_EQUAL, text_insert.substring(0, commonlength)]); | ||
pointer++; | ||
@@ -516,28 +581,28 @@ } | ||
} | ||
// Factor out any common suffixies. | ||
// Factor out any common suffixes. | ||
commonlength = diff_commonSuffix(text_insert, text_delete); | ||
if (commonlength !== 0) { | ||
diffs[pointer][1] = text_insert.substring(text_insert.length - | ||
commonlength) + diffs[pointer][1]; | ||
text_insert = text_insert.substring(0, text_insert.length - | ||
commonlength); | ||
text_delete = text_delete.substring(0, text_delete.length - | ||
commonlength); | ||
diffs[pointer][1] = | ||
text_insert.substring(text_insert.length - commonlength) + diffs[pointer][1]; | ||
text_insert = text_insert.substring(0, text_insert.length - commonlength); | ||
text_delete = text_delete.substring(0, text_delete.length - commonlength); | ||
} | ||
} | ||
// Delete the offending records and add the merged ones. | ||
if (count_delete === 0) { | ||
diffs.splice(pointer - count_insert, | ||
count_delete + count_insert, [DIFF_INSERT, text_insert]); | ||
} else if (count_insert === 0) { | ||
diffs.splice(pointer - count_delete, | ||
count_delete + count_insert, [DIFF_DELETE, text_delete]); | ||
var n = count_insert + count_delete; | ||
if (text_delete.length === 0 && text_insert.length === 0) { | ||
diffs.splice(pointer - n, n); | ||
pointer = pointer - n; | ||
} else if (text_delete.length === 0) { | ||
diffs.splice(pointer - n, n, [DIFF_INSERT, text_insert]); | ||
pointer = pointer - n + 1; | ||
} else if (text_insert.length === 0) { | ||
diffs.splice(pointer - n, n, [DIFF_DELETE, text_delete]); | ||
pointer = pointer - n + 1; | ||
} else { | ||
diffs.splice(pointer - count_delete - count_insert, | ||
count_delete + count_insert, [DIFF_DELETE, text_delete], | ||
[DIFF_INSERT, text_insert]); | ||
diffs.splice(pointer - n, n, [DIFF_DELETE, text_delete], [DIFF_INSERT, text_insert]); | ||
pointer = pointer - n + 2; | ||
} | ||
pointer = pointer - count_delete - count_insert + | ||
(count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1; | ||
} else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) { | ||
} | ||
if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) { | ||
// Merge this equality with the previous one. | ||
@@ -567,11 +632,11 @@ diffs[pointer - 1][1] += diffs[pointer][1]; | ||
while (pointer < diffs.length - 1) { | ||
if (diffs[pointer - 1][0] == DIFF_EQUAL && | ||
diffs[pointer + 1][0] == DIFF_EQUAL) { | ||
if (diffs[pointer - 1][0] === DIFF_EQUAL && | ||
diffs[pointer + 1][0] === DIFF_EQUAL) { | ||
// This is a single edit surrounded by equalities. | ||
if (diffs[pointer][1].substring(diffs[pointer][1].length - | ||
diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) { | ||
diffs[pointer - 1][1].length) === diffs[pointer - 1][1]) { | ||
// Shift the edit over the previous equality. | ||
diffs[pointer][1] = diffs[pointer - 1][1] + | ||
diffs[pointer][1].substring(0, diffs[pointer][1].length - | ||
diffs[pointer - 1][1].length); | ||
diffs[pointer][1].substring(0, diffs[pointer][1].length - | ||
diffs[pointer - 1][1].length); | ||
diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; | ||
@@ -581,8 +646,8 @@ diffs.splice(pointer - 1, 1); | ||
} else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) == | ||
diffs[pointer + 1][1]) { | ||
diffs[pointer + 1][1]) { | ||
// Shift the edit over the next equality. | ||
diffs[pointer - 1][1] += diffs[pointer + 1][1]; | ||
diffs[pointer][1] = | ||
diffs[pointer][1].substring(diffs[pointer + 1][1].length) + | ||
diffs[pointer + 1][1]; | ||
diffs[pointer][1].substring(diffs[pointer + 1][1].length) + | ||
diffs[pointer + 1][1]; | ||
diffs.splice(pointer + 1, 1); | ||
@@ -596,171 +661,142 @@ changes = true; | ||
if (changes) { | ||
diff_cleanupMerge(diffs); | ||
diff_cleanupMerge(diffs, fix_unicode); | ||
} | ||
}; | ||
function is_surrogate_pair_start(charCode) { | ||
return charCode >= 0xD800 && charCode <= 0xDBFF; | ||
} | ||
var diff = diff_main; | ||
diff.INSERT = DIFF_INSERT; | ||
diff.DELETE = DIFF_DELETE; | ||
diff.EQUAL = DIFF_EQUAL; | ||
function is_surrogate_pair_end(charCode) { | ||
return charCode >= 0xDC00 && charCode <= 0xDFFF; | ||
} | ||
module.exports = diff; | ||
function starts_with_pair_end(str) { | ||
return is_surrogate_pair_end(str.charCodeAt(0)); | ||
} | ||
/* | ||
* Modify a diff such that the cursor position points to the start of a change: | ||
* E.g. | ||
* cursor_normalize_diff([[DIFF_EQUAL, 'abc']], 1) | ||
* => [1, [[DIFF_EQUAL, 'a'], [DIFF_EQUAL, 'bc']]] | ||
* cursor_normalize_diff([[DIFF_INSERT, 'new'], [DIFF_DELETE, 'xyz']], 2) | ||
* => [2, [[DIFF_INSERT, 'new'], [DIFF_DELETE, 'xy'], [DIFF_DELETE, 'z']]] | ||
* | ||
* @param {Array} diffs Array of diff tuples | ||
* @param {Int} cursor_pos Suggested edit position. Must not be out of bounds! | ||
* @return {Array} A tuple [cursor location in the modified diff, modified diff] | ||
*/ | ||
function cursor_normalize_diff (diffs, cursor_pos) { | ||
if (cursor_pos === 0) { | ||
return [DIFF_EQUAL, diffs]; | ||
} | ||
for (var current_pos = 0, i = 0; i < diffs.length; i++) { | ||
var d = diffs[i]; | ||
if (d[0] === DIFF_DELETE || d[0] === DIFF_EQUAL) { | ||
var next_pos = current_pos + d[1].length; | ||
if (cursor_pos === next_pos) { | ||
return [i + 1, diffs]; | ||
} else if (cursor_pos < next_pos) { | ||
// copy to prevent side effects | ||
diffs = diffs.slice(); | ||
// split d into two diff changes | ||
var split_pos = cursor_pos - current_pos; | ||
var d_left = [d[0], d[1].slice(0, split_pos)]; | ||
var d_right = [d[0], d[1].slice(split_pos)]; | ||
diffs.splice(i, 1, d_left, d_right); | ||
return [i + 1, diffs]; | ||
} else { | ||
current_pos = next_pos; | ||
} | ||
} | ||
} | ||
throw new Error('cursor_pos is out of bounds!') | ||
function ends_with_pair_start(str) { | ||
return is_surrogate_pair_start(str.charCodeAt(str.length - 1)); | ||
} | ||
/* | ||
* Modify a diff such that the edit position is "shifted" to the proposed edit location (cursor_position). | ||
* | ||
* Case 1) | ||
* Check if a naive shift is possible: | ||
* [0, X], [ 1, Y] -> [ 1, Y], [0, X] (if X + Y === Y + X) | ||
* [0, X], [-1, Y] -> [-1, Y], [0, X] (if X + Y === Y + X) - holds same result | ||
* Case 2) | ||
* Check if the following shifts are possible: | ||
* [0, 'pre'], [ 1, 'prefix'] -> [ 1, 'pre'], [0, 'pre'], [ 1, 'fix'] | ||
* [0, 'pre'], [-1, 'prefix'] -> [-1, 'pre'], [0, 'pre'], [-1, 'fix'] | ||
* ^ ^ | ||
* d d_next | ||
* | ||
* @param {Array} diffs Array of diff tuples | ||
* @param {Int} cursor_pos Suggested edit position. Must not be out of bounds! | ||
* @return {Array} Array of diff tuples | ||
*/ | ||
function fix_cursor (diffs, cursor_pos) { | ||
var norm = cursor_normalize_diff(diffs, cursor_pos); | ||
var ndiffs = norm[1]; | ||
var cursor_pointer = norm[0]; | ||
var d = ndiffs[cursor_pointer]; | ||
var d_next = ndiffs[cursor_pointer + 1]; | ||
if (d == null) { | ||
// Text was deleted from end of original string, | ||
// cursor is now out of bounds in new string | ||
return diffs; | ||
} else if (d[0] !== DIFF_EQUAL) { | ||
// A modification happened at the cursor location. | ||
// This is the expected outcome, so we can return the original diff. | ||
return diffs; | ||
} else { | ||
if (d_next != null && d[1] + d_next[1] === d_next[1] + d[1]) { | ||
// Case 1) | ||
// It is possible to perform a naive shift | ||
ndiffs.splice(cursor_pointer, 2, d_next, d) | ||
return merge_tuples(ndiffs, cursor_pointer, 2) | ||
} else if (d_next != null && d_next[1].indexOf(d[1]) === 0) { | ||
// Case 2) | ||
// d[1] is a prefix of d_next[1] | ||
// We can assume that d_next[0] !== 0, since d[0] === 0 | ||
// Shift edit locations.. | ||
ndiffs.splice(cursor_pointer, 2, [d_next[0], d[1]], [0, d[1]]); | ||
var suffix = d_next[1].slice(d[1].length); | ||
if (suffix.length > 0) { | ||
ndiffs.splice(cursor_pointer + 2, 0, [d_next[0], suffix]); | ||
} | ||
return merge_tuples(ndiffs, cursor_pointer, 3) | ||
} else { | ||
// Not possible to perform any modification | ||
return diffs; | ||
function remove_empty_tuples(tuples) { | ||
var ret = []; | ||
for (var i = 0; i < tuples.length; i++) { | ||
if (tuples[i][1].length > 0) { | ||
ret.push(tuples[i]); | ||
} | ||
} | ||
return ret; | ||
} | ||
/* | ||
* Check diff did not split surrogate pairs. | ||
* Ex. [0, '\uD83D'], [-1, '\uDC36'], [1, '\uDC2F'] -> [-1, '\uD83D\uDC36'], [1, '\uD83D\uDC2F'] | ||
* '\uD83D\uDC36' === '🐶', '\uD83D\uDC2F' === '🐯' | ||
* | ||
* @param {Array} diffs Array of diff tuples | ||
* @return {Array} Array of diff tuples | ||
*/ | ||
function fix_emoji (diffs) { | ||
var compact = false; | ||
var starts_with_pair_end = function(str) { | ||
return str.charCodeAt(0) >= 0xDC00 && str.charCodeAt(0) <= 0xDFFF; | ||
function make_edit_splice(before, oldMiddle, newMiddle, after) { | ||
if (ends_with_pair_start(before) || starts_with_pair_end(after)) { | ||
return null; | ||
} | ||
var ends_with_pair_start = function(str) { | ||
return str.charCodeAt(str.length-1) >= 0xD800 && str.charCodeAt(str.length-1) <= 0xDBFF; | ||
} | ||
for (var i = 2; i < diffs.length; i += 1) { | ||
if (diffs[i-2][0] === DIFF_EQUAL && ends_with_pair_start(diffs[i-2][1]) && | ||
diffs[i-1][0] === DIFF_DELETE && starts_with_pair_end(diffs[i-1][1]) && | ||
diffs[i][0] === DIFF_INSERT && starts_with_pair_end(diffs[i][1])) { | ||
compact = true; | ||
return remove_empty_tuples([ | ||
[DIFF_EQUAL, before], | ||
[DIFF_DELETE, oldMiddle], | ||
[DIFF_INSERT, newMiddle], | ||
[DIFF_EQUAL, after] | ||
]); | ||
} | ||
diffs[i-1][1] = diffs[i-2][1].slice(-1) + diffs[i-1][1]; | ||
diffs[i][1] = diffs[i-2][1].slice(-1) + diffs[i][1]; | ||
diffs[i-2][1] = diffs[i-2][1].slice(0, -1); | ||
function find_cursor_edit_diff(oldText, newText, cursor_pos) { | ||
// note: this runs after equality check has ruled out exact equality | ||
var oldRange = typeof cursor_pos === 'number' ? | ||
{ index: cursor_pos, length: 0 } : cursor_pos.oldRange; | ||
var newRange = typeof cursor_pos === 'number' ? | ||
null : cursor_pos.newRange; | ||
// take into account the old and new selection to generate the best diff | ||
// possible for a text edit. for example, a text change from "xxx" to "xx" | ||
// could be a delete or forwards-delete of any one of the x's, or the | ||
// result of selecting two of the x's and typing "x". | ||
var oldLength = oldText.length; | ||
var newLength = newText.length; | ||
if (oldRange.length === 0 && (newRange === null || newRange.length === 0)) { | ||
// see if we have an insert or delete before or after cursor | ||
var oldCursor = oldRange.index; | ||
var oldBefore = oldText.slice(0, oldCursor); | ||
var oldAfter = oldText.slice(oldCursor); | ||
var maybeNewCursor = newRange ? newRange.index : null; | ||
editBefore: { | ||
// is this an insert or delete right before oldCursor? | ||
var newCursor = oldCursor + newLength - oldLength; | ||
if (maybeNewCursor !== null && maybeNewCursor !== newCursor) { | ||
break editBefore; | ||
} | ||
if (newCursor < 0 || newCursor > newLength) { | ||
break editBefore; | ||
} | ||
var newBefore = newText.slice(0, newCursor); | ||
var newAfter = newText.slice(newCursor); | ||
if (newAfter !== oldAfter) { | ||
break editBefore; | ||
} | ||
var prefixLength = Math.min(oldCursor, newCursor); | ||
var oldPrefix = oldBefore.slice(0, prefixLength); | ||
var newPrefix = newBefore.slice(0, prefixLength); | ||
if (oldPrefix !== newPrefix) { | ||
break editBefore; | ||
} | ||
var oldMiddle = oldBefore.slice(prefixLength); | ||
var newMiddle = newBefore.slice(prefixLength); | ||
return make_edit_splice(oldPrefix, oldMiddle, newMiddle, oldAfter); | ||
} | ||
} | ||
if (!compact) { | ||
return diffs; | ||
} | ||
var fixed_diffs = []; | ||
for (var i = 0; i < diffs.length; i += 1) { | ||
if (diffs[i][1].length > 0) { | ||
fixed_diffs.push(diffs[i]); | ||
editAfter: { | ||
// is this an insert or delete right after oldCursor? | ||
if (maybeNewCursor !== null && maybeNewCursor !== oldCursor) { | ||
break editAfter; | ||
} | ||
var cursor = oldCursor; | ||
var newBefore = newText.slice(0, cursor); | ||
var newAfter = newText.slice(cursor); | ||
if (newBefore !== oldBefore) { | ||
break editAfter; | ||
} | ||
var suffixLength = Math.min(oldLength - cursor, newLength - cursor); | ||
var oldSuffix = oldAfter.slice(oldAfter.length - suffixLength); | ||
var newSuffix = newAfter.slice(newAfter.length - suffixLength); | ||
if (oldSuffix !== newSuffix) { | ||
break editAfter; | ||
} | ||
var oldMiddle = oldAfter.slice(0, oldAfter.length - suffixLength); | ||
var newMiddle = newAfter.slice(0, newAfter.length - suffixLength); | ||
return make_edit_splice(oldBefore, oldMiddle, newMiddle, oldSuffix); | ||
} | ||
} | ||
return fixed_diffs; | ||
} | ||
/* | ||
* Try to merge tuples with their neigbors in a given range. | ||
* E.g. [0, 'a'], [0, 'b'] -> [0, 'ab'] | ||
* | ||
* @param {Array} diffs Array of diff tuples. | ||
* @param {Int} start Position of the first element to merge (diffs[start] is also merged with diffs[start - 1]). | ||
* @param {Int} length Number of consecutive elements to check. | ||
* @return {Array} Array of merged diff tuples. | ||
*/ | ||
function merge_tuples (diffs, start, length) { | ||
// Check from (start-1) to (start+length). | ||
for (var i = start + length - 1; i >= 0 && i >= start - 1; i--) { | ||
if (i + 1 < diffs.length) { | ||
var left_d = diffs[i]; | ||
var right_d = diffs[i+1]; | ||
if (left_d[0] === right_d[1]) { | ||
diffs.splice(i, 2, [left_d[0], left_d[1] + right_d[1]]); | ||
if (oldRange.length > 0 && newRange && newRange.length === 0) { | ||
replaceRange: { | ||
// see if diff could be a splice of the old selection range | ||
var oldPrefix = oldText.slice(0, oldRange.index); | ||
var oldSuffix = oldText.slice(oldRange.index + oldRange.length); | ||
var prefixLength = oldPrefix.length; | ||
var suffixLength = oldSuffix.length; | ||
if (newLength < prefixLength + suffixLength) { | ||
break replaceRange; | ||
} | ||
var newPrefix = newText.slice(0, prefixLength); | ||
var newSuffix = newText.slice(newLength - suffixLength); | ||
if (oldPrefix !== newPrefix || oldSuffix !== newSuffix) { | ||
break replaceRange; | ||
} | ||
var oldMiddle = oldText.slice(prefixLength, oldLength - suffixLength); | ||
var newMiddle = newText.slice(prefixLength, newLength - suffixLength); | ||
return make_edit_splice(oldPrefix, oldMiddle, newMiddle, oldSuffix); | ||
} | ||
} | ||
return diffs; | ||
return null; | ||
} | ||
function diff(text1, text2, cursor_pos) { | ||
// only pass fix_unicode=true at the top level, not when diff_main is | ||
// recursively invoked | ||
return diff_main(text1, text2, cursor_pos, true); | ||
} | ||
diff.INSERT = DIFF_INSERT; | ||
diff.DELETE = DIFF_DELETE; | ||
diff.EQUAL = DIFF_EQUAL; | ||
module.exports = diff; |
{ | ||
"name": "fast-diff", | ||
"version": "1.1.2", | ||
"version": "1.2.0", | ||
"description": "Fast Javascript text diff", | ||
"author": "Jason Chen <jhchen7@gmail.com>", | ||
"main": "diff.js", | ||
"types": "diff.d.ts", | ||
"devDependencies": { | ||
"googlediff": "~0.1.0", | ||
"lodash": "~3.9.3", | ||
@@ -23,3 +23,5 @@ "seedrandom": "~2.4.0" | ||
"license": "Apache-2.0", | ||
"keywords": ["diff"] | ||
"keywords": [ | ||
"diff" | ||
] | ||
} |
347
test.js
var _ = require('lodash'); | ||
var googlediff = require('googlediff'); | ||
var seedrandom = require('seedrandom'); | ||
var diff = require('./diff.js'); | ||
googlediff = new googlediff(); | ||
var ITERATIONS = 10000; | ||
var ALPHABET = 'GATTACA'; | ||
var LENGTH = 100; | ||
var EMOJI_MAX_LENGTH = 50; | ||
@@ -15,2 +13,17 @@ var seed = Math.floor(Math.random() * 10000); | ||
console.log('Running regression tests...'); | ||
[ | ||
['GAATAAAAAAAGATTAACAT', 'AAAAACTTGTAATTAACAAC'], | ||
['🔘🤘🔗🔗', '🔗🤗🤗__🤗🤘🤘🤗🔗🤘🔗'], | ||
['🔗🤗🤗__🤗🤘🤘🤗🔗🤘🔗', '🤗🤘🔘'], | ||
['🤘🤘🔘🔘_🔘🔗🤘🤗🤗__🔗🤘', '🤘🔘🤘🔗🤘🤘🔗🤗🤘🔘🔘'], | ||
['🤗🤘🤗🔘🤘🔘🤗_🤗🔗🤘🤗_🤘🔗🤗🤘🔗🤘🤘🤘🔗🤗🔗🔗🔗🤗_🤘🔗🤗🤗🔘🤗🤗🤘🤗', | ||
'_🤗🤘_🤘🤘🔘🤗🔘🤘_🔘🤗🔗🔘🔗🤘🔗🤘🤗🔗🔗🔗🤘🔘_🤗🤘🤘🤘__🤘_🔘🤘🤘_🔗🤘🔘'], | ||
['🔗🤘🤗🔘🔘🤗', '🤘🤘🤘🤗🔘🔗🔗'], | ||
['🔘_🔗🔗🔗🤗🔗', '🤘🤗🔗🤗_🤘🔘_'], | ||
].forEach(function (data) { | ||
var result = diff(data[0], data[1]); | ||
applyDiff(result, data[0], data[1]); | ||
}); | ||
console.log('Running computing ' + ITERATIONS + ' diffs with seed ' + seed + '...'); | ||
@@ -20,5 +33,5 @@ | ||
var strings = []; | ||
for(var i = 0; i <= ITERATIONS; ++i) { | ||
for (var i = 0; i <= ITERATIONS; ++i) { | ||
var chars = []; | ||
for(var l = 0; l < LENGTH; ++l) { | ||
for (var l = 0; l < LENGTH; ++l) { | ||
var letter = ALPHABET.substr(Math.floor(random() * ALPHABET.length), 1); | ||
@@ -30,65 +43,295 @@ chars.push(letter); | ||
console.log('Running tests *without* cursor information...'); | ||
for(var i = 0; i < ITERATIONS; ++i) { | ||
var result = diff(strings[i], strings[i+1]); | ||
var expected = googlediff.diff_main(strings[i], strings[i+1]); | ||
if (!_.isEqual(result, expected)) { | ||
console.log('Expected', expected); | ||
console.log('Result', result); | ||
throw new Error('Diff produced difference results.'); | ||
} | ||
console.log('Running fuzz tests *without* cursor information...'); | ||
for (var i = 0; i < ITERATIONS; ++i) { | ||
var result = diff(strings[i], strings[i + 1]); | ||
applyDiff(result, strings[i], strings[i + 1]); | ||
} | ||
console.log('Running tests *with* cursor information'); | ||
for(var i = 0; i < ITERATIONS; ++i) { | ||
console.log('Running fuzz tests *with* cursor information'); | ||
for (var i = 0; i < ITERATIONS; ++i) { | ||
var cursor_pos = Math.floor(random() * strings[i].length + 1); | ||
var diffs = diff(strings[i], strings[i+1], cursor_pos); | ||
var patch = googlediff.patch_make(strings[i], strings[i+1], diffs); | ||
var expected = googlediff.patch_apply(patch, strings[i])[0]; | ||
if (expected !== strings[i+1]) { | ||
console.log('Expected', expected); | ||
console.log('Result', strings[i+1]); | ||
throw new Error('Diff produced difference results.'); | ||
var diffs = diff(strings[i], strings[i + 1], cursor_pos); | ||
applyDiff(diffs, strings[i], strings[i + 1]); | ||
} | ||
function parseDiff(str) { | ||
if (!str) { | ||
return []; | ||
} | ||
return str.split(/(?=[+\-=])/).map(function (piece) { | ||
var symbol = piece.charAt(0); | ||
var text = piece.slice(1); | ||
return [ | ||
symbol === '+' ? diff.INSERT : symbol === '-' ? diff.DELETE : diff.EQUAL, | ||
text | ||
] | ||
}); | ||
} | ||
console.log('Running emoji tests'); | ||
(function() { | ||
var result = diff('🐶', '🐯'); | ||
var expected = [ | ||
[diff.DELETE, '🐶'], | ||
[diff.INSERT, '🐯'], | ||
]; | ||
if (!_.isEqual(result, expected)) { | ||
console.log(result, '!==', expected); | ||
throw new Error('Emoji simple case test failed'); | ||
console.log('Running cursor tests'); | ||
[ | ||
['', 0, '', null, ''], | ||
['', 0, 'a', null, '+a'], | ||
['a', 0, 'aa', null, '+a=a'], | ||
['a', 1, 'aa', null, '=a+a'], | ||
['aa', 0, 'aaa', null, '+a=aa'], | ||
['aa', 1, 'aaa', null, '=a+a=a'], | ||
['aa', 2, 'aaa', null, '=aa+a'], | ||
['aaa', 0, 'aaaa', null, '+a=aaa'], | ||
['aaa', 1, 'aaaa', null, '=a+a=aa'], | ||
['aaa', 2, 'aaaa', null, '=aa+a=a'], | ||
['aaa', 3, 'aaaa', null, '=aaa+a'], | ||
['a', 0, '', null, '-a'], | ||
['a', 1, '', null, '-a'], | ||
['aa', 0, 'a', null, '-a=a'], | ||
['aa', 1, 'a', null, '-a=a'], | ||
['aa', 2, 'a', null, '=a-a'], | ||
['aaa', 0, 'aa', null, '-a=aa'], | ||
['aaa', 1, 'aa', null, '-a=aa'], | ||
['aaa', 2, 'aa', null, '=a-a=a'], | ||
['aaa', 3, 'aa', null, '=aa-a'], | ||
['', 0, '', 0, ''], | ||
['', 0, 'a', 1, '+a'], | ||
['a', 0, 'aa', 1, '+a=a'], | ||
['a', 1, 'aa', 2, '=a+a'], | ||
['aa', 0, 'aaa', 1, '+a=aa'], | ||
['aa', 1, 'aaa', 2, '=a+a=a'], | ||
['aa', 2, 'aaa', 3, '=aa+a'], | ||
['aaa', 0, 'aaaa', 1, '+a=aaa'], | ||
['aaa', 1, 'aaaa', 2, '=a+a=aa'], | ||
['aaa', 2, 'aaaa', 3, '=aa+a=a'], | ||
['aaa', 3, 'aaaa', 4, '=aaa+a'], | ||
['a', 1, '', 0, '-a'], | ||
['aa', 1, 'a', 0, '-a=a'], | ||
['aa', 2, 'a', 1, '=a-a'], | ||
['aaa', 1, 'aa', 0, '-a=aa'], | ||
['aaa', 2, 'aa', 1, '=a-a=a'], | ||
['aaa', 3, 'aa', 2, '=aa-a'], | ||
['a', 1, '', 0, '-a'], | ||
['aa', 1, 'a', 0, '-a=a'], | ||
['aa', 2, 'a', 1, '=a-a'], | ||
['aaa', 1, 'aa', 0, '-a=aa'], | ||
['aaa', 2, 'aa', 1, '=a-a=a'], | ||
['aaa', 3, 'aa', 2, '=aa-a'], | ||
// forward-delete | ||
['a', 0, '', 0, '-a'], | ||
['aa', 0, 'a', 0, '-a=a'], | ||
['aa', 1, 'a', 1, '=a-a'], | ||
['aaa', 0, 'aa', 0, '-a=aa'], | ||
['aaa', 1, 'aa', 1, '=a-a=a'], | ||
['aaa', 2, 'aa', 2, '=aa-a'], | ||
['bob', 0, 'bobob', null, '+bo=bob'], | ||
['bob', 1, 'bobob', null, '=b+ob=ob'], | ||
['bob', 2, 'bobob', null, '=bo+bo=b'], | ||
['bob', 3, 'bobob', null, '=bob+ob'], | ||
['bob', 0, 'bobob', 2, '+bo=bob'], | ||
['bob', 1, 'bobob', 3, '=b+ob=ob'], | ||
['bob', 2, 'bobob', 4, '=bo+bo=b'], | ||
['bob', 3, 'bobob', 5, '=bob+ob'], | ||
['bobob', 2, 'bob', null, '-bo=bob'], | ||
['bobob', 3, 'bob', null, '=b-ob=ob'], | ||
['bobob', 4, 'bob', null, '=bo-bo=b'], | ||
['bobob', 5, 'bob', null, '=bob-ob'], | ||
['bobob', 2, 'bob', 0, '-bo=bob'], | ||
['bobob', 3, 'bob', 1, '=b-ob=ob'], | ||
['bobob', 4, 'bob', 2, '=bo-bo=b'], | ||
['bobob', 5, 'bob', 3, '=bob-ob'], | ||
['bob', 1, 'b', null, '=b-ob'], | ||
['hello', [0, 5], 'h', 1, '-hello+h'], | ||
['yay', [0, 3], 'y', 1, '-yay+y'], | ||
['bobob', [1, 4], 'bob', 2, '=b-obo+o=b'], | ||
].forEach(function (data) { | ||
var oldText = data[0]; | ||
var newText = data[2]; | ||
var oldRange = typeof data[1] === 'number' ? | ||
{ index: data[1], length: 0 } : | ||
{ index: data[1][0], length: data[1][1] - data[1][0] }; | ||
var newRange = typeof data[3] === 'number' ? | ||
{ index: data[3], length: 0 } : | ||
data[3] === null ? null : { index: data[3][0], length: data[3][1] - data[3][0] }; | ||
var expected = parseDiff(data[4]); | ||
if (newRange === null && typeof data[1] !== 'number') { | ||
throw new Error('invalid test case'); | ||
} | ||
})(); | ||
var cursorInfo = newRange === null ? data[1] : { | ||
oldRange: oldRange, | ||
newRange: newRange, | ||
}; | ||
doCursorTest(oldText, newText, cursorInfo, expected); | ||
doCursorTest('x' + oldText, 'x' + newText, shiftCursorInfo(cursorInfo, 1), diffPrepend(expected, 'x')); | ||
doCursorTest(oldText + 'x', newText + 'x', cursorInfo, diffAppend(expected, 'x')); | ||
}); | ||
(function() { | ||
var result = diff('👨🏽', '👩🏽'); | ||
var expected = [ | ||
[diff.DELETE, '👨'], | ||
[diff.INSERT, '👩'], | ||
[diff.EQUAL, '🏽'] | ||
]; | ||
function diffPrepend(tuples, text) { | ||
if (tuples.length > 0 && tuples[0][0] === diff.EQUAL) { | ||
return [[diff.EQUAL, text + tuples[0][1]]].concat(tuples.slice(1)); | ||
} else { | ||
return [[diff.EQUAL, text]].concat(tuples); | ||
} | ||
} | ||
function diffAppend(tuples, text) { | ||
var lastTuple = tuples[tuples.length - 1]; | ||
if (lastTuple && lastTuple[0] === diff.EQUAL) { | ||
return tuples.slice(0, -1).concat([[diff.EQUAL, lastTuple[1] + text]]); | ||
} else { | ||
return tuples.concat([[diff.EQUAL, text]]); | ||
} | ||
} | ||
function shiftCursorInfo(cursorInfo, amount) { | ||
if (typeof cursorInfo === 'number') { | ||
return cursorInfo + amount; | ||
} else { | ||
return { | ||
oldRange: { | ||
index: cursorInfo.oldRange.index + amount, | ||
length: cursorInfo.oldRange.length, | ||
}, | ||
newRange: { | ||
index: cursorInfo.newRange.index + amount, | ||
length: cursorInfo.newRange.length, | ||
}, | ||
} | ||
} | ||
} | ||
function doCursorTest(oldText, newText, cursorInfo, expected) { | ||
var result = diff(oldText, newText, cursorInfo); | ||
if (!_.isEqual(result, expected)) { | ||
console.log([oldText, newText, cursorInfo]); | ||
console.log(result, '!==', expected); | ||
throw new Error('Emoji before case test failed'); | ||
throw new Error('cursor test failed'); | ||
} | ||
})(); | ||
} | ||
(function() { | ||
var result = diff('👩🏼', '👩🏽'); | ||
var expected = [ | ||
[diff.EQUAL, '👩'], | ||
[diff.DELETE, '🏼'], | ||
[diff.INSERT, '🏽'], | ||
]; | ||
console.log('Running emoji tests'); | ||
[ | ||
['🐶', '🐯', '-🐶+🐯'], | ||
['👨🏽', '👩🏽', '-👨+👩=🏽'], | ||
['👩🏼', '👩🏽', '=👩-🏼+🏽'], | ||
['🍏🍎', '🍎', '-🍏=🍎'], | ||
['🍎', '🍏🍎', '+🍏=🍎'], | ||
].forEach(function (data) { | ||
var oldText = data[0]; | ||
var newText = data[1]; | ||
var expected = parseDiff(data[2]); | ||
doEmojiTest(oldText, newText, expected); | ||
doEmojiTest('x' + oldText, 'x' + newText, diffPrepend(expected, 'x')); | ||
doEmojiTest(oldText + 'x', newText + 'x', diffAppend(expected, 'x')); | ||
}); | ||
function doEmojiTest(oldText, newText, expected) { | ||
var result = diff(oldText, newText); | ||
if (!_.isEqual(result, expected)) { | ||
console.log(oldText, newText, expected); | ||
console.log(result, '!==', expected); | ||
throw new Error('Emoji after case test failed'); | ||
throw new Error('Emoji simple test case failed'); | ||
} | ||
})(); | ||
} | ||
// emojis chosen to share high and low surrogates! | ||
var EMOJI_ALPHABET = ['_', '🤗', '🔗', '🤘', '🔘']; | ||
console.log('Generating emoji strings...'); | ||
var emoji_strings = []; | ||
for (var i = 0; i <= ITERATIONS; ++i) { | ||
var letters = []; | ||
var len = Math.floor(random() * EMOJI_MAX_LENGTH); | ||
for (var l = 0; l < len; ++l) { | ||
var letter = EMOJI_ALPHABET[Math.floor(random() * EMOJI_ALPHABET.length)]; | ||
letters.push(letter); | ||
} | ||
emoji_strings.push(letters.join('')); | ||
} | ||
console.log('Running emoji fuzz tests...'); | ||
for (var i = 0; i < ITERATIONS; ++i) { | ||
var oldText = emoji_strings[i]; | ||
var newText = emoji_strings[i + 1]; | ||
var result = diff(oldText, newText); | ||
applyDiff(result, oldText, newText); | ||
} | ||
// Applies a diff to text, throwing an error if diff is invalid or incorrect | ||
function applyDiff(diffs, text, expectedResult) { | ||
var pos = 0; | ||
function throwError(message) { | ||
console.log(diffs, text, expectedResult); | ||
throw new Error(message); | ||
} | ||
function expect(expected) { | ||
var found = text.substr(pos, expected.length); | ||
if (found !== expected) { | ||
throwError('Expected "' + expected + '", found "' + found + '"'); | ||
} | ||
} | ||
var result = ''; | ||
var inserts_since_last_equality = 0; | ||
var deletes_since_last_equality = 0; | ||
for (var i = 0; i < diffs.length; i++) { | ||
var d = diffs[i]; | ||
if (!d[1]) { | ||
throwError('Empty tuple in diff') | ||
} | ||
var firstCharCode = d[1].charCodeAt(0); | ||
var lastCharCode = d[1].slice(-1).charCodeAt(0); | ||
if (firstCharCode >= 0xDC00 && firstCharCode <= 0xDFFF || | ||
lastCharCode >= 0xD800 && lastCharCode <= 0xDBFF) { | ||
throwError('Bad unicode diff tuple') | ||
} | ||
switch (d[0]) { | ||
case diff.EQUAL: | ||
if (i !== 0 && !inserts_since_last_equality && !deletes_since_last_equality) { | ||
throwError('two consecutive equalities in diff'); | ||
} | ||
inserts_since_last_equality = 0; | ||
deletes_since_last_equality = 0; | ||
expect(d[1]); | ||
result += d[1]; | ||
pos += d[1].length; | ||
break; | ||
case diff.DELETE: | ||
if (deletes_since_last_equality) { | ||
throwError('multiple deletes between equalities') | ||
} | ||
if (inserts_since_last_equality) { | ||
throwError('delete following insert in diff') | ||
} | ||
deletes_since_last_equality++; | ||
expect(d[1]); | ||
pos += d[1].length; | ||
break | ||
case diff.INSERT: | ||
if (inserts_since_last_equality) { | ||
throwError('multiple inserts between equalities') | ||
} | ||
inserts_since_last_equality++; | ||
result += d[1]; | ||
break; | ||
} | ||
} | ||
if (pos !== text.length) { | ||
throwError('Diff did not consume entire input text'); | ||
} | ||
if (result !== expectedResult) { | ||
console.log(diffs, text, expectedResult, result); | ||
throw new Error('Diff not correct') | ||
} | ||
return result; | ||
} | ||
console.log("Success!"); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
39898
2
6
1039
1