<NME> Operation.js
<BEF> /*@flow*/
/*
 * Copyright 2014 XWiki SAS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
"use strict";
var Common = require('./Common');

var Operation = module.exports;

/*::
export type Operation_t = {
    type: 'Operation',
    offset: number,
    toRemove: number,
    toInsert: string
};
export type Operation_Packed_t = [number, number, string];
export type Operation_Simplify_t = (Operation_t, string, typeof(Operation.simplify))=>?Operation_t;
export type Operation_Transform_t = (string, Operation_t, Operation_t)=>?Operation_t;
*/

var check = Operation.check = function (op /*:any*/, docLength_opt /*:?number*/) /*:Operation_t*/ {
    Common.assert(op.type === 'Operation');
    if (!Common.isUint(op.offset)) { throw new Error(); }
    if (!Common.isUint(op.toRemove)) { throw new Error(); }
    if (typeof(op.toInsert) !== 'string') { throw new Error(); }
    if (op.toRemove < 1 && op.toInsert.length < 1) { throw new Error(); }
    Common.assert(typeof(docLength_opt) !== 'number' || op.offset + op.toRemove <= docLength_opt);
    return op;
};

var create = Operation.create = function (
    offset /*:?number*/,
    toRemove /*:?number*/,
    toInsert /*:?string*/)
{
    var out = {
        type: 'Operation',
        offset: offset || 0,
        toRemove: toRemove || 0,
        toInsert: toInsert || '',
    };
    if (Common.PARANOIA) { check(out); }
    return Object.freeze(out);
};

Operation.toObj = function (op /*:Operation_t*/) {
    if (Common.PARANOIA) { check(op); }
    return [op.offset,op.toRemove,op.toInsert];
};

 // Allow any as input because we assert its type internally..
Operation.fromObj = function (obj /*:any*/) {
    Common.assert(Array.isArray(obj) && obj.length === 3);
    return create(obj[0], obj[1], obj[2]);
};

/**
 * @param op the operation to apply.
 * @param doc the content to apply the operation on
 */
var apply = Operation.apply = function (op /*:Operation_t*/, doc /*:string*/)
{
    if (Common.PARANOIA) {
        Common.assert(typeof(doc) === 'string');
        check(op, doc.length);
    }
    return doc.substring(0,op.offset) + op.toInsert + doc.substring(op.offset + op.toRemove);
};

Operation.applyMulti = function (ops /*:Array<Operation_t>*/, doc /*:string*/)
{
    for (var i = ops.length - 1; i >= 0; i--) { doc = apply(ops[i], doc); }
    return doc;
};

var invert = Operation.invert = function (op /*:Operation_t*/, doc /*:string*/) {
    if (Common.PARANOIA) {
        check(op);
        Common.assert(typeof(doc) === 'string');
        Common.assert(op.offset + op.toRemove <= doc.length);
    }
    return create(
        op.offset,
        op.toInsert.length,
        // https://stackoverflow.com/a/31733628
        (' ' + doc.substring(op.offset, op.offset + op.toRemove)).slice(1)
    );
};

// see http://unicode.org/faq/utf_bom.html#utf16-7
var surrogatePattern = /[\uD800-\uDBFF]|[\uDC00-\uDFFF]/;
    return surrogatePattern.test(str);
};

var simplify = Operation.simplify = function (op /*:Operation_t*/, doc /*:string*/) {
    if (Common.PARANOIA) {
        check(op);
        check(op);
        Common.assert(typeof(doc) === 'string');
        Common.assert(op.offset + op.toRemove <= doc.length);
    }
    var rop = invert(op, doc);

    var minLen = Math.min(op.toInsert.length, rop.toInsert.length);
    var i = 0;
    while (i < minLen && rop.toInsert[i] === op.toInsert[i]) {
        if (hasSurrogate(rop.toInsert[i]) || hasSurrogate(op.toInsert[i])) {
            if (op.toInsert[i + 1] === rop.toInsert[i + 1]) {
                i++;
            } else {
                break;
            }
        }
        i++;
    }
    var opOffset = op.offset + i;
    var opToInsert = op.toInsert.substring(i);
    var ropToInsert = rop.toInsert.substring(i);

    var j;
    for (i = ropToInsert.length - 1, j = opToInsert.length - 1; i >= 0 && j >= 0 &&
        ropToInsert.charAt(i) === opToInsert.charAt(j); i--, j--) ;
    opToInsert = opToInsert.substring(0, j + 1);
    opToRemove = i + 1;

    if (opToRemove === 0 && opToInsert.length === 0) { return null; }
    return create(opOffset, opToRemove, opToInsert);
    return create(opOffset, opToRemove, opToInsert);
};

Operation.equals = function (opA /*:Operation_t*/, opB /*:Operation_t*/) {
    return (opA.toRemove === opB.toRemove
        && opA.toInsert === opB.toInsert
        && opA.offset === opB.offset);
};

Operation.lengthChange = function (op /*:Operation_t*/)
{
    if (Common.PARANOIA) { check(op); }
    return op.toInsert.length - op.toRemove;
};

/*
 * @return the merged operation OR null if the result of the merger is a noop.
 */
Operation.merge = function (oldOpOrig /*:Operation_t*/, newOpOrig /*:Operation_t*/) {
    if (Common.PARANOIA) {
        check(newOpOrig);
        check(oldOpOrig);
    }

    var oldOp_offset = oldOpOrig.offset;
    var oldOp_toRemove = oldOpOrig.toRemove;
    var oldOp_toInsert = oldOpOrig.toInsert;

    var newOp_offset = newOpOrig.offset;
    var newOp_toRemove = newOpOrig.toRemove;
    var newOp_toInsert = newOpOrig.toInsert;

    var offsetDiff = newOp_offset - oldOp_offset;

    if (newOp_toRemove > 0) {
        var origOldInsert = oldOp_toInsert;
        oldOp_toInsert = (
             oldOp_toInsert.substring(0,offsetDiff)
           + oldOp_toInsert.substring(offsetDiff + newOp_toRemove)
        );
        newOp_toRemove -= (origOldInsert.length - oldOp_toInsert.length);
        if (newOp_toRemove < 0) { newOp_toRemove = 0; }

        oldOp_toRemove += newOp_toRemove;
        newOp_toRemove = 0;
    }

    if (offsetDiff < 0) {
        oldOp_offset += offsetDiff;
        oldOp_toInsert = newOp_toInsert + oldOp_toInsert;

    } else if (oldOp_toInsert.length === offsetDiff) {
        oldOp_toInsert = oldOp_toInsert + newOp_toInsert;

    } else if (oldOp_toInsert.length > offsetDiff) {
        oldOp_toInsert = (
            oldOp_toInsert.substring(0,offsetDiff)
          + newOp_toInsert
          + oldOp_toInsert.substring(offsetDiff)
        );
    } else {
        throw new Error("should never happen\n" +
                        JSON.stringify([oldOpOrig,newOpOrig], null, '  '));
    }

    if (oldOp_toInsert === '' && oldOp_toRemove === 0) { return null; }

    return create(oldOp_offset, oldOp_toRemove, oldOp_toInsert);
};

/**
 * If the new operation deletes what the old op inserted or inserts content in the middle of
 * the old op's content or if they abbut one another, they should be merged.
 */
Operation.shouldMerge = function (oldOp /*:Operation_t*/, newOp /*:Operation_t*/)
{
    if (Common.PARANOIA) {
        check(oldOp);
        check(newOp);
    }
    if (newOp.offset < oldOp.offset) {
        return (oldOp.offset <= (newOp.offset + newOp.toRemove));
    } else {
        return (newOp.offset <= (oldOp.offset + oldOp.toInsert.length));
    }
};

/**
 * Rebase newOp against oldOp.
 *
 * @param oldOp the eariler operation to have happened.
 * @param newOp the later operation to have happened (in time).
 * @return either the untouched newOp if it need not be rebased,
 *                the rebased clone of newOp if it needs rebasing, or
 *                null if newOp and oldOp must be merged.
 */
Operation.rebase = function (oldOp /*:Operation_t*/, newOp /*:Operation_t*/) {
    if (Common.PARANOIA) {
        check(oldOp);
        check(newOp);
    }
    if (newOp.offset < oldOp.offset) { return newOp; }
    return create(
        newOp.offset + oldOp.toRemove - oldOp.toInsert.length,
        newOp.toRemove,
        newOp.toInsert
    );
};

/** Used for testing. */
Operation.random = function (docLength /*:number*/) {
    Common.assert(Common.isUint(docLength));
    var offset = Math.floor(Math.random() * 100000000 % docLength) || 0;
    var toRemove = Math.floor(Math.random() * 100000000 % (docLength - offset)) || 0;
    var toInsert = '';
    do {
        toInsert = Common.randomASCII(Math.floor(Math.random() * 20));
    } while (toRemove === 0 && toInsert === '');
    return create(offset, toRemove, toInsert);
};

Object.freeze(module.exports);

<MSG> ♩ Can't touch this ♬

<DFF> @@ -106,6 +106,14 @@ var hasSurrogate = Operation.hasSurrogate = function(str /*:string*/) {
     return surrogatePattern.test(str);
 };
 
+/**
+ * ATTENTION: This function is not just a neat way to make patches smaller, it's
+ *            actually part of the ChainPad consensus rules, so if you have a clever
+ *            idea to make it a bit faster, it is going to cause ChainPad to reject
+ *            old patches, which means when you go to load the history of a pad, you're
+ *            sunk.
+ * tl;dr can't touch this
+ */
 var simplify = Operation.simplify = function (op /*:Operation_t*/, doc /*:string*/) {
     if (Common.PARANOIA) {
         check(op);
@@ -131,11 +139,11 @@ var simplify = Operation.simplify = function (op /*:Operation_t*/, doc /*:string
     var opToInsert = op.toInsert.substring(i);
     var ropToInsert = rop.toInsert.substring(i);
 
-    var j;
-    for (i = ropToInsert.length - 1, j = opToInsert.length - 1; i >= 0 && j >= 0 &&
-        ropToInsert.charAt(i) === opToInsert.charAt(j); i--, j--) ;
-    opToInsert = opToInsert.substring(0, j + 1);
-    opToRemove = i + 1;
+    if (ropToInsert.length === opToInsert.length) {
+        for (i = ropToInsert.length-1; i >= 0 && ropToInsert[i] === opToInsert[i]; i--) ;
+        opToInsert = opToInsert.substring(0, i+1);
+        opToRemove = i+1;
+    }
 
     if (opToRemove === 0 && opToInsert.length === 0) { return null; }
     return create(opOffset, opToRemove, opToInsert);

{'commit': "diff --git a/client/Operation.js b/client/Operation.js\nindex 2c14432..023516c 100644\n--- a/client/Operation.js\n+++ b/client/Operation.js\n@@ -106,6 +106,14 @@ var hasSurrogate = Operation.hasSurrogate = function(str /*:string*/) {\n     return surrogatePattern.test(str);\n };\n \n+/**\n+ * ATTENTION: This function is not just a neat way to make patches smaller, it's\n+ *            actually part of the ChainPad consensus rules, so if you have a clever\n+ *            idea to make it a bit faster, it is going to cause ChainPad to reject\n+ *            old patches, which means when you go to load the history of a pad, you're\n+ *            sunk.\n+ * tl;dr can't touch this\n+ */\n var simplify = Operation.simplify = function (op /*:Operation_t*/, doc /*:string*/) {\n     if (Common.PARANOIA) {\n         check(op);\n@@ -131,11 +139,11 @@ var simplify = Operation.simplify = function (op /*:Operation_t*/, doc /*:string\n     var opToInsert = op.toInsert.substring(i);\n     var ropToInsert = rop.toInsert.substring(i);\n \n-    var j;\n-    for (i = ropToInsert.length - 1, j = opToInsert.length - 1; i >= 0 && j >= 0 &&\n-        ropToInsert.charAt(i) === opToInsert.charAt(j); i--, j--) ;\n-    opToInsert = opToInsert.substring(0, j + 1);\n-    opToRemove = i + 1;\n+    if (ropToInsert.length === opToInsert.length) {\n+        for (i = ropToInsert.length-1; i >= 0 && ropToInsert[i] === opToInsert[i]; i--) ;\n+        opToInsert = opToInsert.substring(0, i+1);\n+        opToRemove = i+1;\n+    }\n \n     if (opToRemove === 0 && opToInsert.length === 0) { return null; }\n     return create(opOffset, opToRemove, opToInsert);\n", 'commit_subject': "♩ Can't touch this ♬", 'repo_name': 'xwiki-contrib/chainpad', 'committer': {'name': 'Caleb James DeLisle', 'email': '5c953cab9e28ed64725061f5b5430a17a78b8b78@cjdns.fr', 'time_sec': '1510247208', 'tz_offset': '60', 'date': {'seconds': '1510247208'}}, 'commit_tree': 'b5220c92899bd65d5657fcb24a7adce26a7142f5', 'commit_message': "♩ Can't touch this ♬\n", 'language_name': 'JavaScript', 'license': 'agpl-3.0', 'hunks': "@@ -106,6 +106,14 @@ var hasSurrogate = Operation.hasSurrogate = function(str /*:string*/) {\n     return surrogatePattern.test(str);\n };\n \n+/**\n+ * ATTENTION: This function is not just a neat way to make patches smaller, it's\n+ *            actually part of the ChainPad consensus rules, so if you have a clever\n+ *            idea to make it a bit faster, it is going to cause ChainPad to reject\n+ *            old patches, which means when you go to load the history of a pad, you're\n+ *            sunk.\n+ * tl;dr can't touch this\n+ */\n var simplify = Operation.simplify = function (op /*:Operation_t*/, doc /*:string*/) {\n     if (Common.PARANOIA) {\n         check(op);\n@@ -131,11 +139,11 @@ var simplify = Operation.simplify = function (op /*:Operation_t*/, doc /*:string\n     var opToInsert = op.toInsert.substring(i);\n     var ropToInsert = rop.toInsert.substring(i);\n \n-    var j;\n-    for (i = ropToInsert.length - 1, j = opToInsert.length - 1; i >= 0 && j >= 0 &&\n-        ropToInsert.charAt(i) === opToInsert.charAt(j); i--, j--) ;\n-    opToInsert = opToInsert.substring(0, j + 1);\n-    opToRemove = i + 1;\n+    if (ropToInsert.length === opToInsert.length) {\n+        for (i = ropToInsert.length-1; i >= 0 && ropToInsert[i] === opToInsert[i]; i--) ;\n+        opToInsert = opToInsert.substring(0, i+1);\n+        opToRemove = i+1;\n+    }\n \n     if (opToRemove === 0 && opToInsert.length === 0) { return null; }\n     return create(opOffset, opToRemove, opToInsert);\n", 'addition_count': 13, 'deletion_count': 5, 'src_file': 'a/client/Operation.js', 'tgt_file': 'b/client/Operation.js', 'file_extension': '.js', 'before_file': '/*@flow*/\n/*\n * Copyright 2014 XWiki SAS\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n"use strict";\nvar Common = require(\'./Common\');\n\nvar Operation = module.exports;\n\n/*::\nexport type Operation_t = {\n    type: \'Operation\',\n    offset: number,\n    toRemove: number,\n    toInsert: string\n};\nexport type Operation_Packed_t = [number, number, string];\nexport type Operation_Simplify_t = (Operation_t, string, typeof(Operation.simplify))=>?Operation_t;\nexport type Operation_Transform_t = (string, Operation_t, Operation_t)=>?Operation_t;\n*/\n\nvar check = Operation.check = function (op /*:any*/, docLength_opt /*:?number*/) /*:Operation_t*/ {\n    Common.assert(op.type === \'Operation\');\n    if (!Common.isUint(op.offset)) { throw new Error(); }\n    if (!Common.isUint(op.toRemove)) { throw new Error(); }\n    if (typeof(op.toInsert) !== \'string\') { throw new Error(); }\n    if (op.toRemove < 1 && op.toInsert.length < 1) { throw new Error(); }\n    Common.assert(typeof(docLength_opt) !== \'number\' || op.offset + op.toRemove <= docLength_opt);\n    return op;\n};\n\nvar create = Operation.create = function (\n    offset /*:?number*/,\n    toRemove /*:?number*/,\n    toInsert /*:?string*/)\n{\n    var out = {\n        type: \'Operation\',\n        offset: offset || 0,\n        toRemove: toRemove || 0,\n        toInsert: toInsert || \'\',\n    };\n    if (Common.PARANOIA) { check(out); }\n    return Object.freeze(out);\n};\n\nOperation.toObj = function (op /*:Operation_t*/) {\n    if (Common.PARANOIA) { check(op); }\n    return [op.offset,op.toRemove,op.toInsert];\n};\n\n // Allow any as input because we assert its type internally..\nOperation.fromObj = function (obj /*:any*/) {\n    Common.assert(Array.isArray(obj) && obj.length === 3);\n    return create(obj[0], obj[1], obj[2]);\n};\n\n/**\n * @param op the operation to apply.\n * @param doc the content to apply the operation on\n */\nvar apply = Operation.apply = function (op /*:Operation_t*/, doc /*:string*/)\n{\n    if (Common.PARANOIA) {\n        Common.assert(typeof(doc) === \'string\');\n        check(op, doc.length);\n    }\n    return doc.substring(0,op.offset) + op.toInsert + doc.substring(op.offset + op.toRemove);\n};\n\nOperation.applyMulti = function (ops /*:Array<Operation_t>*/, doc /*:string*/)\n{\n    for (var i = ops.length - 1; i >= 0; i--) { doc = apply(ops[i], doc); }\n    return doc;\n};\n\nvar invert = Operation.invert = function (op /*:Operation_t*/, doc /*:string*/) {\n    if (Common.PARANOIA) {\n        check(op);\n        Common.assert(typeof(doc) === \'string\');\n        Common.assert(op.offset + op.toRemove <= doc.length);\n    }\n    return create(\n        op.offset,\n        op.toInsert.length,\n        // https://stackoverflow.com/a/31733628\n        (\' \' + doc.substring(op.offset, op.offset + op.toRemove)).slice(1)\n    );\n};\n\n// see http://unicode.org/faq/utf_bom.html#utf16-7\nvar surrogatePattern = /[\\uD800-\\uDBFF]|[\\uDC00-\\uDFFF]/;\n    return surrogatePattern.test(str);\n};\n\nvar simplify = Operation.simplify = function (op /*:Operation_t*/, doc /*:string*/) {\n    if (Common.PARANOIA) {\n        check(op);\n        check(op);\n        Common.assert(typeof(doc) === \'string\');\n        Common.assert(op.offset + op.toRemove <= doc.length);\n    }\n    var rop = invert(op, doc);\n\n    var minLen = Math.min(op.toInsert.length, rop.toInsert.length);\n    var i = 0;\n    while (i < minLen && rop.toInsert[i] === op.toInsert[i]) {\n        if (hasSurrogate(rop.toInsert[i]) || hasSurrogate(op.toInsert[i])) {\n            if (op.toInsert[i + 1] === rop.toInsert[i + 1]) {\n                i++;\n            } else {\n                break;\n            }\n        }\n        i++;\n    }\n    var opOffset = op.offset + i;\n    var opToInsert = op.toInsert.substring(i);\n    var ropToInsert = rop.toInsert.substring(i);\n\n    var j;\n    for (i = ropToInsert.length - 1, j = opToInsert.length - 1; i >= 0 && j >= 0 &&\n        ropToInsert.charAt(i) === opToInsert.charAt(j); i--, j--) ;\n    opToInsert = opToInsert.substring(0, j + 1);\n    opToRemove = i + 1;\n\n    if (opToRemove === 0 && opToInsert.length === 0) { return null; }\n    return create(opOffset, opToRemove, opToInsert);\n    return create(opOffset, opToRemove, opToInsert);\n};\n\nOperation.equals = function (opA /*:Operation_t*/, opB /*:Operation_t*/) {\n    return (opA.toRemove === opB.toRemove\n        && opA.toInsert === opB.toInsert\n        && opA.offset === opB.offset);\n};\n\nOperation.lengthChange = function (op /*:Operation_t*/)\n{\n    if (Common.PARANOIA) { check(op); }\n    return op.toInsert.length - op.toRemove;\n};\n\n/*\n * @return the merged operation OR null if the result of the merger is a noop.\n */\nOperation.merge = function (oldOpOrig /*:Operation_t*/, newOpOrig /*:Operation_t*/) {\n    if (Common.PARANOIA) {\n        check(newOpOrig);\n        check(oldOpOrig);\n    }\n\n    var oldOp_offset = oldOpOrig.offset;\n    var oldOp_toRemove = oldOpOrig.toRemove;\n    var oldOp_toInsert = oldOpOrig.toInsert;\n\n    var newOp_offset = newOpOrig.offset;\n    var newOp_toRemove = newOpOrig.toRemove;\n    var newOp_toInsert = newOpOrig.toInsert;\n\n    var offsetDiff = newOp_offset - oldOp_offset;\n\n    if (newOp_toRemove > 0) {\n        var origOldInsert = oldOp_toInsert;\n        oldOp_toInsert = (\n             oldOp_toInsert.substring(0,offsetDiff)\n           + oldOp_toInsert.substring(offsetDiff + newOp_toRemove)\n        );\n        newOp_toRemove -= (origOldInsert.length - oldOp_toInsert.length);\n        if (newOp_toRemove < 0) { newOp_toRemove = 0; }\n\n        oldOp_toRemove += newOp_toRemove;\n        newOp_toRemove = 0;\n    }\n\n    if (offsetDiff < 0) {\n        oldOp_offset += offsetDiff;\n        oldOp_toInsert = newOp_toInsert + oldOp_toInsert;\n\n    } else if (oldOp_toInsert.length === offsetDiff) {\n        oldOp_toInsert = oldOp_toInsert + newOp_toInsert;\n\n    } else if (oldOp_toInsert.length > offsetDiff) {\n        oldOp_toInsert = (\n            oldOp_toInsert.substring(0,offsetDiff)\n          + newOp_toInsert\n          + oldOp_toInsert.substring(offsetDiff)\n        );\n    } else {\n        throw new Error("should never happen\\n" +\n                        JSON.stringify([oldOpOrig,newOpOrig], null, \'  \'));\n    }\n\n    if (oldOp_toInsert === \'\' && oldOp_toRemove === 0) { return null; }\n\n    return create(oldOp_offset, oldOp_toRemove, oldOp_toInsert);\n};\n\n/**\n * If the new operation deletes what the old op inserted or inserts content in the middle of\n * the old op\'s content or if they abbut one another, they should be merged.\n */\nOperation.shouldMerge = function (oldOp /*:Operation_t*/, newOp /*:Operation_t*/)\n{\n    if (Common.PARANOIA) {\n        check(oldOp);\n        check(newOp);\n    }\n    if (newOp.offset < oldOp.offset) {\n        return (oldOp.offset <= (newOp.offset + newOp.toRemove));\n    } else {\n        return (newOp.offset <= (oldOp.offset + oldOp.toInsert.length));\n    }\n};\n\n/**\n * Rebase newOp against oldOp.\n *\n * @param oldOp the eariler operation to have happened.\n * @param newOp the later operation to have happened (in time).\n * @return either the untouched newOp if it need not be rebased,\n *                the rebased clone of newOp if it needs rebasing, or\n *                null if newOp and oldOp must be merged.\n */\nOperation.rebase = function (oldOp /*:Operation_t*/, newOp /*:Operation_t*/) {\n    if (Common.PARANOIA) {\n        check(oldOp);\n        check(newOp);\n    }\n    if (newOp.offset < oldOp.offset) { return newOp; }\n    return create(\n        newOp.offset + oldOp.toRemove - oldOp.toInsert.length,\n        newOp.toRemove,\n        newOp.toInsert\n    );\n};\n\n/** Used for testing. */\nOperation.random = function (docLength /*:number*/) {\n    Common.assert(Common.isUint(docLength));\n    var offset = Math.floor(Math.random() * 100000000 % docLength) || 0;\n    var toRemove = Math.floor(Math.random() * 100000000 % (docLength - offset)) || 0;\n    var toInsert = \'\';\n    do {\n        toInsert = Common.randomASCII(Math.floor(Math.random() * 20));\n    } while (toRemove === 0 && toInsert === \'\');\n    return create(offset, toRemove, toInsert);\n};\n\nObject.freeze(module.exports);\n', 'message': "♩ Can't touch this ♬\n", 'author_name': 'Caleb James DeLisle', 'lang': 'js'}