├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── diff.d.ts ├── diff.js ├── package-lock.json ├── package.json └── test.js /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Git checkout 14 | uses: actions/checkout@v2 15 | 16 | - uses: actions/setup-node@v2 17 | - run: npm install 18 | - run: npm test 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nyc_output 3 | coverage 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014-2023 Jason Chen 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fast Diff ![Build Status](https://github.com/jhchen/fast-diff/actions/workflows/test.yml/badge.svg) 2 | 3 | This is a simplified import of the excellent [diff-match-patch](https://code.google.com/p/google-diff-match-patch/) library by [Neil Fraser](https://neil.fraser.name/) into the Node.js environment. The match and patch parts are removed, as well as all the extra diff options. What remains is incredibly fast diffing between two strings. 4 | 5 | The diff function is an implementation of ["An O(ND) Difference Algorithm and its Variations" (Myers, 1986)](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.4.6927&rep=rep1&type=pdf) with the suggested divide and conquer strategy along with several [optimizations](http://neil.fraser.name/news/2007/10/09/) Neil added. 6 | 7 | ```js 8 | var diff = require('fast-diff'); 9 | 10 | var good = 'Good dog'; 11 | var bad = 'Bad dog'; 12 | 13 | var result = diff(good, bad); 14 | // [[-1, "Goo"], [1, "Ba"], [0, "d dog"]] 15 | 16 | // Respect suggested edit location (cursor position), added in v1.1 17 | diff('aaa', 'aaaa', 1) 18 | // [[0, "a"], [1, "a"], [0, "aa"]] 19 | 20 | // For convenience 21 | diff.INSERT === 1; 22 | diff.EQUAL === 0; 23 | diff.DELETE === -1; 24 | ``` 25 | -------------------------------------------------------------------------------- /diff.d.ts: -------------------------------------------------------------------------------- 1 | declare function diff( 2 | text1: string, 3 | text2: string, 4 | cursorPos?: number | diff.CursorInfo, 5 | cleanup?: boolean 6 | ): diff.Diff[]; 7 | 8 | declare namespace diff { 9 | type Diff = [-1 | 0 | 1, string]; 10 | 11 | const DELETE: -1; 12 | const INSERT: 1; 13 | const EQUAL: 0; 14 | 15 | interface CursorInfo { 16 | oldRange: { index: number; length: number }; 17 | newRange: { index: number; length: number }; 18 | } 19 | } 20 | 21 | export = diff; 22 | -------------------------------------------------------------------------------- /diff.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This library modifies the diff-patch-match library by Neil Fraser 3 | * by removing the patch and match functionality and certain advanced 4 | * options in the diff function. The original license is as follows: 5 | * 6 | * === 7 | * 8 | * Diff Match and Patch 9 | * 10 | * Copyright 2006 Google Inc. 11 | * http://code.google.com/p/google-diff-match-patch/ 12 | * 13 | * Licensed under the Apache License, Version 2.0 (the "License"); 14 | * you may not use this file except in compliance with the License. 15 | * You may obtain a copy of the License at 16 | * 17 | * http://www.apache.org/licenses/LICENSE-2.0 18 | * 19 | * Unless required by applicable law or agreed to in writing, software 20 | * distributed under the License is distributed on an "AS IS" BASIS, 21 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | * See the License for the specific language governing permissions and 23 | * limitations under the License. 24 | */ 25 | 26 | /** 27 | * The data structure representing a diff is an array of tuples: 28 | * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] 29 | * which means: delete 'Hello', add 'Goodbye' and keep ' world.' 30 | */ 31 | var DIFF_DELETE = -1; 32 | var DIFF_INSERT = 1; 33 | var DIFF_EQUAL = 0; 34 | 35 | /** 36 | * Find the differences between two texts. Simplifies the problem by stripping 37 | * any common prefix or suffix off the texts before diffing. 38 | * @param {string} text1 Old string to be diffed. 39 | * @param {string} text2 New string to be diffed. 40 | * @param {Int|Object} [cursor_pos] Edit position in text1 or object with more info 41 | * @param {boolean} [cleanup] Apply semantic cleanup before returning. 42 | * @return {Array} Array of diff tuples. 43 | */ 44 | function diff_main(text1, text2, cursor_pos, cleanup, _fix_unicode) { 45 | // Check for equality 46 | if (text1 === text2) { 47 | if (text1) { 48 | return [[DIFF_EQUAL, text1]]; 49 | } 50 | return []; 51 | } 52 | 53 | if (cursor_pos != null) { 54 | var editdiff = find_cursor_edit_diff(text1, text2, cursor_pos); 55 | if (editdiff) { 56 | return editdiff; 57 | } 58 | } 59 | 60 | // Trim off common prefix (speedup). 61 | var commonlength = diff_commonPrefix(text1, text2); 62 | var commonprefix = text1.substring(0, commonlength); 63 | text1 = text1.substring(commonlength); 64 | text2 = text2.substring(commonlength); 65 | 66 | // Trim off common suffix (speedup). 67 | commonlength = diff_commonSuffix(text1, text2); 68 | var commonsuffix = text1.substring(text1.length - commonlength); 69 | text1 = text1.substring(0, text1.length - commonlength); 70 | text2 = text2.substring(0, text2.length - commonlength); 71 | 72 | // Compute the diff on the middle block. 73 | var diffs = diff_compute_(text1, text2); 74 | 75 | // Restore the prefix and suffix. 76 | if (commonprefix) { 77 | diffs.unshift([DIFF_EQUAL, commonprefix]); 78 | } 79 | if (commonsuffix) { 80 | diffs.push([DIFF_EQUAL, commonsuffix]); 81 | } 82 | diff_cleanupMerge(diffs, _fix_unicode); 83 | if (cleanup) { 84 | diff_cleanupSemantic(diffs); 85 | } 86 | return diffs; 87 | } 88 | 89 | /** 90 | * Find the differences between two texts. Assumes that the texts do not 91 | * have any common prefix or suffix. 92 | * @param {string} text1 Old string to be diffed. 93 | * @param {string} text2 New string to be diffed. 94 | * @return {Array} Array of diff tuples. 95 | */ 96 | function diff_compute_(text1, text2) { 97 | var diffs; 98 | 99 | if (!text1) { 100 | // Just add some text (speedup). 101 | return [[DIFF_INSERT, text2]]; 102 | } 103 | 104 | if (!text2) { 105 | // Just delete some text (speedup). 106 | return [[DIFF_DELETE, text1]]; 107 | } 108 | 109 | var longtext = text1.length > text2.length ? text1 : text2; 110 | var shorttext = text1.length > text2.length ? text2 : text1; 111 | var i = longtext.indexOf(shorttext); 112 | if (i !== -1) { 113 | // Shorter text is inside the longer text (speedup). 114 | diffs = [ 115 | [DIFF_INSERT, longtext.substring(0, i)], 116 | [DIFF_EQUAL, shorttext], 117 | [DIFF_INSERT, longtext.substring(i + shorttext.length)], 118 | ]; 119 | // Swap insertions for deletions if diff is reversed. 120 | if (text1.length > text2.length) { 121 | diffs[0][0] = diffs[2][0] = DIFF_DELETE; 122 | } 123 | return diffs; 124 | } 125 | 126 | if (shorttext.length === 1) { 127 | // Single character string. 128 | // After the previous speedup, the character can't be an equality. 129 | return [ 130 | [DIFF_DELETE, text1], 131 | [DIFF_INSERT, text2], 132 | ]; 133 | } 134 | 135 | // Check to see if the problem can be split in two. 136 | var hm = diff_halfMatch_(text1, text2); 137 | if (hm) { 138 | // A half-match was found, sort out the return data. 139 | var text1_a = hm[0]; 140 | var text1_b = hm[1]; 141 | var text2_a = hm[2]; 142 | var text2_b = hm[3]; 143 | var mid_common = hm[4]; 144 | // Send both pairs off for separate processing. 145 | var diffs_a = diff_main(text1_a, text2_a); 146 | var diffs_b = diff_main(text1_b, text2_b); 147 | // Merge the results. 148 | return diffs_a.concat([[DIFF_EQUAL, mid_common]], diffs_b); 149 | } 150 | 151 | return diff_bisect_(text1, text2); 152 | } 153 | 154 | /** 155 | * Find the 'middle snake' of a diff, split the problem in two 156 | * and return the recursively constructed diff. 157 | * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. 158 | * @param {string} text1 Old string to be diffed. 159 | * @param {string} text2 New string to be diffed. 160 | * @return {Array} Array of diff tuples. 161 | * @private 162 | */ 163 | function diff_bisect_(text1, text2) { 164 | // Cache the text lengths to prevent multiple calls. 165 | var text1_length = text1.length; 166 | var text2_length = text2.length; 167 | var max_d = Math.ceil((text1_length + text2_length) / 2); 168 | var v_offset = max_d; 169 | var v_length = 2 * max_d; 170 | var v1 = new Array(v_length); 171 | var v2 = new Array(v_length); 172 | // Setting all elements to -1 is faster in Chrome & Firefox than mixing 173 | // integers and undefined. 174 | for (var x = 0; x < v_length; x++) { 175 | v1[x] = -1; 176 | v2[x] = -1; 177 | } 178 | v1[v_offset + 1] = 0; 179 | v2[v_offset + 1] = 0; 180 | var delta = text1_length - text2_length; 181 | // If the total number of characters is odd, then the front path will collide 182 | // with the reverse path. 183 | var front = delta % 2 !== 0; 184 | // Offsets for start and end of k loop. 185 | // Prevents mapping of space beyond the grid. 186 | var k1start = 0; 187 | var k1end = 0; 188 | var k2start = 0; 189 | var k2end = 0; 190 | for (var d = 0; d < max_d; d++) { 191 | // Walk the front path one step. 192 | for (var k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { 193 | var k1_offset = v_offset + k1; 194 | var x1; 195 | if (k1 === -d || (k1 !== d && v1[k1_offset - 1] < v1[k1_offset + 1])) { 196 | x1 = v1[k1_offset + 1]; 197 | } else { 198 | x1 = v1[k1_offset - 1] + 1; 199 | } 200 | var y1 = x1 - k1; 201 | while ( 202 | x1 < text1_length && 203 | y1 < text2_length && 204 | text1.charAt(x1) === text2.charAt(y1) 205 | ) { 206 | x1++; 207 | y1++; 208 | } 209 | v1[k1_offset] = x1; 210 | if (x1 > text1_length) { 211 | // Ran off the right of the graph. 212 | k1end += 2; 213 | } else if (y1 > text2_length) { 214 | // Ran off the bottom of the graph. 215 | k1start += 2; 216 | } else if (front) { 217 | var k2_offset = v_offset + delta - k1; 218 | if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] !== -1) { 219 | // Mirror x2 onto top-left coordinate system. 220 | var x2 = text1_length - v2[k2_offset]; 221 | if (x1 >= x2) { 222 | // Overlap detected. 223 | return diff_bisectSplit_(text1, text2, x1, y1); 224 | } 225 | } 226 | } 227 | } 228 | 229 | // Walk the reverse path one step. 230 | for (var k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { 231 | var k2_offset = v_offset + k2; 232 | var x2; 233 | if (k2 === -d || (k2 !== d && v2[k2_offset - 1] < v2[k2_offset + 1])) { 234 | x2 = v2[k2_offset + 1]; 235 | } else { 236 | x2 = v2[k2_offset - 1] + 1; 237 | } 238 | var y2 = x2 - k2; 239 | while ( 240 | x2 < text1_length && 241 | y2 < text2_length && 242 | text1.charAt(text1_length - x2 - 1) === 243 | text2.charAt(text2_length - y2 - 1) 244 | ) { 245 | x2++; 246 | y2++; 247 | } 248 | v2[k2_offset] = x2; 249 | if (x2 > text1_length) { 250 | // Ran off the left of the graph. 251 | k2end += 2; 252 | } else if (y2 > text2_length) { 253 | // Ran off the top of the graph. 254 | k2start += 2; 255 | } else if (!front) { 256 | var k1_offset = v_offset + delta - k2; 257 | if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] !== -1) { 258 | var x1 = v1[k1_offset]; 259 | var y1 = v_offset + x1 - k1_offset; 260 | // Mirror x2 onto top-left coordinate system. 261 | x2 = text1_length - x2; 262 | if (x1 >= x2) { 263 | // Overlap detected. 264 | return diff_bisectSplit_(text1, text2, x1, y1); 265 | } 266 | } 267 | } 268 | } 269 | } 270 | // Diff took too long and hit the deadline or 271 | // number of diffs equals number of characters, no commonality at all. 272 | return [ 273 | [DIFF_DELETE, text1], 274 | [DIFF_INSERT, text2], 275 | ]; 276 | } 277 | 278 | /** 279 | * Given the location of the 'middle snake', split the diff in two parts 280 | * and recurse. 281 | * @param {string} text1 Old string to be diffed. 282 | * @param {string} text2 New string to be diffed. 283 | * @param {number} x Index of split point in text1. 284 | * @param {number} y Index of split point in text2. 285 | * @return {Array} Array of diff tuples. 286 | */ 287 | function diff_bisectSplit_(text1, text2, x, y) { 288 | var text1a = text1.substring(0, x); 289 | var text2a = text2.substring(0, y); 290 | var text1b = text1.substring(x); 291 | var text2b = text2.substring(y); 292 | 293 | // Compute both diffs serially. 294 | var diffs = diff_main(text1a, text2a); 295 | var diffsb = diff_main(text1b, text2b); 296 | 297 | return diffs.concat(diffsb); 298 | } 299 | 300 | /** 301 | * Determine the common prefix of two strings. 302 | * @param {string} text1 First string. 303 | * @param {string} text2 Second string. 304 | * @return {number} The number of characters common to the start of each 305 | * string. 306 | */ 307 | function diff_commonPrefix(text1, text2) { 308 | // Quick check for common null cases. 309 | if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) { 310 | return 0; 311 | } 312 | // Binary search. 313 | // Performance analysis: http://neil.fraser.name/news/2007/10/09/ 314 | var pointermin = 0; 315 | var pointermax = Math.min(text1.length, text2.length); 316 | var pointermid = pointermax; 317 | var pointerstart = 0; 318 | while (pointermin < pointermid) { 319 | if ( 320 | text1.substring(pointerstart, pointermid) == 321 | text2.substring(pointerstart, pointermid) 322 | ) { 323 | pointermin = pointermid; 324 | pointerstart = pointermin; 325 | } else { 326 | pointermax = pointermid; 327 | } 328 | pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); 329 | } 330 | 331 | if (is_surrogate_pair_start(text1.charCodeAt(pointermid - 1))) { 332 | pointermid--; 333 | } 334 | 335 | return pointermid; 336 | } 337 | 338 | /** 339 | * Determine if the suffix of one string is the prefix of another. 340 | * @param {string} text1 First string. 341 | * @param {string} text2 Second string. 342 | * @return {number} The number of characters common to the end of the first 343 | * string and the start of the second string. 344 | * @private 345 | */ 346 | function diff_commonOverlap_(text1, text2) { 347 | // Cache the text lengths to prevent multiple calls. 348 | var text1_length = text1.length; 349 | var text2_length = text2.length; 350 | // Eliminate the null case. 351 | if (text1_length == 0 || text2_length == 0) { 352 | return 0; 353 | } 354 | // Truncate the longer string. 355 | if (text1_length > text2_length) { 356 | text1 = text1.substring(text1_length - text2_length); 357 | } else if (text1_length < text2_length) { 358 | text2 = text2.substring(0, text1_length); 359 | } 360 | var text_length = Math.min(text1_length, text2_length); 361 | // Quick check for the worst case. 362 | if (text1 == text2) { 363 | return text_length; 364 | } 365 | 366 | // Start by looking for a single character match 367 | // and increase length until no match is found. 368 | // Performance analysis: http://neil.fraser.name/news/2010/11/04/ 369 | var best = 0; 370 | var length = 1; 371 | while (true) { 372 | var pattern = text1.substring(text_length - length); 373 | var found = text2.indexOf(pattern); 374 | if (found == -1) { 375 | return best; 376 | } 377 | length += found; 378 | if ( 379 | found == 0 || 380 | text1.substring(text_length - length) == text2.substring(0, length) 381 | ) { 382 | best = length; 383 | length++; 384 | } 385 | } 386 | } 387 | 388 | /** 389 | * Determine the common suffix of two strings. 390 | * @param {string} text1 First string. 391 | * @param {string} text2 Second string. 392 | * @return {number} The number of characters common to the end of each string. 393 | */ 394 | function diff_commonSuffix(text1, text2) { 395 | // Quick check for common null cases. 396 | if (!text1 || !text2 || text1.slice(-1) !== text2.slice(-1)) { 397 | return 0; 398 | } 399 | // Binary search. 400 | // Performance analysis: http://neil.fraser.name/news/2007/10/09/ 401 | var pointermin = 0; 402 | var pointermax = Math.min(text1.length, text2.length); 403 | var pointermid = pointermax; 404 | var pointerend = 0; 405 | while (pointermin < pointermid) { 406 | if ( 407 | text1.substring(text1.length - pointermid, text1.length - pointerend) == 408 | text2.substring(text2.length - pointermid, text2.length - pointerend) 409 | ) { 410 | pointermin = pointermid; 411 | pointerend = pointermin; 412 | } else { 413 | pointermax = pointermid; 414 | } 415 | pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); 416 | } 417 | 418 | if (is_surrogate_pair_end(text1.charCodeAt(text1.length - pointermid))) { 419 | pointermid--; 420 | } 421 | 422 | return pointermid; 423 | } 424 | 425 | /** 426 | * Do the two texts share a substring which is at least half the length of the 427 | * longer text? 428 | * This speedup can produce non-minimal diffs. 429 | * @param {string} text1 First string. 430 | * @param {string} text2 Second string. 431 | * @return {Array.} Five element Array, containing the prefix of 432 | * text1, the suffix of text1, the prefix of text2, the suffix of 433 | * text2 and the common middle. Or null if there was no match. 434 | */ 435 | function diff_halfMatch_(text1, text2) { 436 | var longtext = text1.length > text2.length ? text1 : text2; 437 | var shorttext = text1.length > text2.length ? text2 : text1; 438 | if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { 439 | return null; // Pointless. 440 | } 441 | 442 | /** 443 | * Does a substring of shorttext exist within longtext such that the substring 444 | * is at least half the length of longtext? 445 | * Closure, but does not reference any external variables. 446 | * @param {string} longtext Longer string. 447 | * @param {string} shorttext Shorter string. 448 | * @param {number} i Start index of quarter length substring within longtext. 449 | * @return {Array.} Five element Array, containing the prefix of 450 | * longtext, the suffix of longtext, the prefix of shorttext, the suffix 451 | * of shorttext and the common middle. Or null if there was no match. 452 | * @private 453 | */ 454 | function diff_halfMatchI_(longtext, shorttext, i) { 455 | // Start with a 1/4 length substring at position i as a seed. 456 | var seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); 457 | var j = -1; 458 | var best_common = ""; 459 | var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b; 460 | while ((j = shorttext.indexOf(seed, j + 1)) !== -1) { 461 | var prefixLength = diff_commonPrefix( 462 | longtext.substring(i), 463 | shorttext.substring(j) 464 | ); 465 | var suffixLength = diff_commonSuffix( 466 | longtext.substring(0, i), 467 | shorttext.substring(0, j) 468 | ); 469 | if (best_common.length < suffixLength + prefixLength) { 470 | best_common = 471 | shorttext.substring(j - suffixLength, j) + 472 | shorttext.substring(j, j + prefixLength); 473 | best_longtext_a = longtext.substring(0, i - suffixLength); 474 | best_longtext_b = longtext.substring(i + prefixLength); 475 | best_shorttext_a = shorttext.substring(0, j - suffixLength); 476 | best_shorttext_b = shorttext.substring(j + prefixLength); 477 | } 478 | } 479 | if (best_common.length * 2 >= longtext.length) { 480 | return [ 481 | best_longtext_a, 482 | best_longtext_b, 483 | best_shorttext_a, 484 | best_shorttext_b, 485 | best_common, 486 | ]; 487 | } else { 488 | return null; 489 | } 490 | } 491 | 492 | // First check if the second quarter is the seed for a half-match. 493 | var hm1 = diff_halfMatchI_( 494 | longtext, 495 | shorttext, 496 | Math.ceil(longtext.length / 4) 497 | ); 498 | // Check again based on the third quarter. 499 | var hm2 = diff_halfMatchI_( 500 | longtext, 501 | shorttext, 502 | Math.ceil(longtext.length / 2) 503 | ); 504 | var hm; 505 | if (!hm1 && !hm2) { 506 | return null; 507 | } else if (!hm2) { 508 | hm = hm1; 509 | } else if (!hm1) { 510 | hm = hm2; 511 | } else { 512 | // Both matched. Select the longest. 513 | hm = hm1[4].length > hm2[4].length ? hm1 : hm2; 514 | } 515 | 516 | // A half-match was found, sort out the return data. 517 | var text1_a, text1_b, text2_a, text2_b; 518 | if (text1.length > text2.length) { 519 | text1_a = hm[0]; 520 | text1_b = hm[1]; 521 | text2_a = hm[2]; 522 | text2_b = hm[3]; 523 | } else { 524 | text2_a = hm[0]; 525 | text2_b = hm[1]; 526 | text1_a = hm[2]; 527 | text1_b = hm[3]; 528 | } 529 | var mid_common = hm[4]; 530 | return [text1_a, text1_b, text2_a, text2_b, mid_common]; 531 | } 532 | 533 | /** 534 | * Reduce the number of edits by eliminating semantically trivial equalities. 535 | * @param {!Array.} diffs Array of diff tuples. 536 | */ 537 | function diff_cleanupSemantic(diffs) { 538 | var changes = false; 539 | var equalities = []; // Stack of indices where equalities are found. 540 | var equalitiesLength = 0; // Keeping our own length var is faster in JS. 541 | /** @type {?string} */ 542 | var lastequality = null; 543 | // Always equal to diffs[equalities[equalitiesLength - 1]][1] 544 | var pointer = 0; // Index of current position. 545 | // Number of characters that changed prior to the equality. 546 | var length_insertions1 = 0; 547 | var length_deletions1 = 0; 548 | // Number of characters that changed after the equality. 549 | var length_insertions2 = 0; 550 | var length_deletions2 = 0; 551 | while (pointer < diffs.length) { 552 | if (diffs[pointer][0] == DIFF_EQUAL) { 553 | // Equality found. 554 | equalities[equalitiesLength++] = pointer; 555 | length_insertions1 = length_insertions2; 556 | length_deletions1 = length_deletions2; 557 | length_insertions2 = 0; 558 | length_deletions2 = 0; 559 | lastequality = diffs[pointer][1]; 560 | } else { 561 | // An insertion or deletion. 562 | if (diffs[pointer][0] == DIFF_INSERT) { 563 | length_insertions2 += diffs[pointer][1].length; 564 | } else { 565 | length_deletions2 += diffs[pointer][1].length; 566 | } 567 | // Eliminate an equality that is smaller or equal to the edits on both 568 | // sides of it. 569 | if ( 570 | lastequality && 571 | lastequality.length <= 572 | Math.max(length_insertions1, length_deletions1) && 573 | lastequality.length <= Math.max(length_insertions2, length_deletions2) 574 | ) { 575 | // Duplicate record. 576 | diffs.splice(equalities[equalitiesLength - 1], 0, [ 577 | DIFF_DELETE, 578 | lastequality, 579 | ]); 580 | // Change second copy to insert. 581 | diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; 582 | // Throw away the equality we just deleted. 583 | equalitiesLength--; 584 | // Throw away the previous equality (it needs to be reevaluated). 585 | equalitiesLength--; 586 | pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; 587 | length_insertions1 = 0; // Reset the counters. 588 | length_deletions1 = 0; 589 | length_insertions2 = 0; 590 | length_deletions2 = 0; 591 | lastequality = null; 592 | changes = true; 593 | } 594 | } 595 | pointer++; 596 | } 597 | 598 | // Normalize the diff. 599 | if (changes) { 600 | diff_cleanupMerge(diffs); 601 | } 602 | diff_cleanupSemanticLossless(diffs); 603 | 604 | // Find any overlaps between deletions and insertions. 605 | // e.g: abcxxxxxxdef 606 | // -> abcxxxdef 607 | // e.g: xxxabcdefxxx 608 | // -> defxxxabc 609 | // Only extract an overlap if it is as big as the edit ahead or behind it. 610 | pointer = 1; 611 | while (pointer < diffs.length) { 612 | if ( 613 | diffs[pointer - 1][0] == DIFF_DELETE && 614 | diffs[pointer][0] == DIFF_INSERT 615 | ) { 616 | var deletion = diffs[pointer - 1][1]; 617 | var insertion = diffs[pointer][1]; 618 | var overlap_length1 = diff_commonOverlap_(deletion, insertion); 619 | var overlap_length2 = diff_commonOverlap_(insertion, deletion); 620 | if (overlap_length1 >= overlap_length2) { 621 | if ( 622 | overlap_length1 >= deletion.length / 2 || 623 | overlap_length1 >= insertion.length / 2 624 | ) { 625 | // Overlap found. Insert an equality and trim the surrounding edits. 626 | diffs.splice(pointer, 0, [ 627 | DIFF_EQUAL, 628 | insertion.substring(0, overlap_length1), 629 | ]); 630 | diffs[pointer - 1][1] = deletion.substring( 631 | 0, 632 | deletion.length - overlap_length1 633 | ); 634 | diffs[pointer + 1][1] = insertion.substring(overlap_length1); 635 | pointer++; 636 | } 637 | } else { 638 | if ( 639 | overlap_length2 >= deletion.length / 2 || 640 | overlap_length2 >= insertion.length / 2 641 | ) { 642 | // Reverse overlap found. 643 | // Insert an equality and swap and trim the surrounding edits. 644 | diffs.splice(pointer, 0, [ 645 | DIFF_EQUAL, 646 | deletion.substring(0, overlap_length2), 647 | ]); 648 | diffs[pointer - 1][0] = DIFF_INSERT; 649 | diffs[pointer - 1][1] = insertion.substring( 650 | 0, 651 | insertion.length - overlap_length2 652 | ); 653 | diffs[pointer + 1][0] = DIFF_DELETE; 654 | diffs[pointer + 1][1] = deletion.substring(overlap_length2); 655 | pointer++; 656 | } 657 | } 658 | pointer++; 659 | } 660 | pointer++; 661 | } 662 | } 663 | 664 | var nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/; 665 | var whitespaceRegex_ = /\s/; 666 | var linebreakRegex_ = /[\r\n]/; 667 | var blanklineEndRegex_ = /\n\r?\n$/; 668 | var blanklineStartRegex_ = /^\r?\n\r?\n/; 669 | 670 | /** 671 | * Look for single edits surrounded on both sides by equalities 672 | * which can be shifted sideways to align the edit to a word boundary. 673 | * e.g: The cat came. -> The cat came. 674 | * @param {!Array.} diffs Array of diff tuples. 675 | */ 676 | function diff_cleanupSemanticLossless(diffs) { 677 | /** 678 | * Given two strings, compute a score representing whether the internal 679 | * boundary falls on logical boundaries. 680 | * Scores range from 6 (best) to 0 (worst). 681 | * Closure, but does not reference any external variables. 682 | * @param {string} one First string. 683 | * @param {string} two Second string. 684 | * @return {number} The score. 685 | * @private 686 | */ 687 | function diff_cleanupSemanticScore_(one, two) { 688 | if (!one || !two) { 689 | // Edges are the best. 690 | return 6; 691 | } 692 | 693 | // Each port of this function behaves slightly differently due to 694 | // subtle differences in each language's definition of things like 695 | // 'whitespace'. Since this function's purpose is largely cosmetic, 696 | // the choice has been made to use each language's native features 697 | // rather than force total conformity. 698 | var char1 = one.charAt(one.length - 1); 699 | var char2 = two.charAt(0); 700 | var nonAlphaNumeric1 = char1.match(nonAlphaNumericRegex_); 701 | var nonAlphaNumeric2 = char2.match(nonAlphaNumericRegex_); 702 | var whitespace1 = nonAlphaNumeric1 && char1.match(whitespaceRegex_); 703 | var whitespace2 = nonAlphaNumeric2 && char2.match(whitespaceRegex_); 704 | var lineBreak1 = whitespace1 && char1.match(linebreakRegex_); 705 | var lineBreak2 = whitespace2 && char2.match(linebreakRegex_); 706 | var blankLine1 = lineBreak1 && one.match(blanklineEndRegex_); 707 | var blankLine2 = lineBreak2 && two.match(blanklineStartRegex_); 708 | 709 | if (blankLine1 || blankLine2) { 710 | // Five points for blank lines. 711 | return 5; 712 | } else if (lineBreak1 || lineBreak2) { 713 | // Four points for line breaks. 714 | return 4; 715 | } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) { 716 | // Three points for end of sentences. 717 | return 3; 718 | } else if (whitespace1 || whitespace2) { 719 | // Two points for whitespace. 720 | return 2; 721 | } else if (nonAlphaNumeric1 || nonAlphaNumeric2) { 722 | // One point for non-alphanumeric. 723 | return 1; 724 | } 725 | return 0; 726 | } 727 | 728 | var pointer = 1; 729 | // Intentionally ignore the first and last element (don't need checking). 730 | while (pointer < diffs.length - 1) { 731 | if ( 732 | diffs[pointer - 1][0] == DIFF_EQUAL && 733 | diffs[pointer + 1][0] == DIFF_EQUAL 734 | ) { 735 | // This is a single edit surrounded by equalities. 736 | var equality1 = diffs[pointer - 1][1]; 737 | var edit = diffs[pointer][1]; 738 | var equality2 = diffs[pointer + 1][1]; 739 | 740 | // First, shift the edit as far left as possible. 741 | var commonOffset = diff_commonSuffix(equality1, edit); 742 | if (commonOffset) { 743 | var commonString = edit.substring(edit.length - commonOffset); 744 | equality1 = equality1.substring(0, equality1.length - commonOffset); 745 | edit = commonString + edit.substring(0, edit.length - commonOffset); 746 | equality2 = commonString + equality2; 747 | } 748 | 749 | // Second, step character by character right, looking for the best fit. 750 | var bestEquality1 = equality1; 751 | var bestEdit = edit; 752 | var bestEquality2 = equality2; 753 | var bestScore = 754 | diff_cleanupSemanticScore_(equality1, edit) + 755 | diff_cleanupSemanticScore_(edit, equality2); 756 | while (edit.charAt(0) === equality2.charAt(0)) { 757 | equality1 += edit.charAt(0); 758 | edit = edit.substring(1) + equality2.charAt(0); 759 | equality2 = equality2.substring(1); 760 | var score = 761 | diff_cleanupSemanticScore_(equality1, edit) + 762 | diff_cleanupSemanticScore_(edit, equality2); 763 | // The >= encourages trailing rather than leading whitespace on edits. 764 | if (score >= bestScore) { 765 | bestScore = score; 766 | bestEquality1 = equality1; 767 | bestEdit = edit; 768 | bestEquality2 = equality2; 769 | } 770 | } 771 | 772 | if (diffs[pointer - 1][1] != bestEquality1) { 773 | // We have an improvement, save it back to the diff. 774 | if (bestEquality1) { 775 | diffs[pointer - 1][1] = bestEquality1; 776 | } else { 777 | diffs.splice(pointer - 1, 1); 778 | pointer--; 779 | } 780 | diffs[pointer][1] = bestEdit; 781 | if (bestEquality2) { 782 | diffs[pointer + 1][1] = bestEquality2; 783 | } else { 784 | diffs.splice(pointer + 1, 1); 785 | pointer--; 786 | } 787 | } 788 | } 789 | pointer++; 790 | } 791 | } 792 | 793 | /** 794 | * Reorder and merge like edit sections. Merge equalities. 795 | * Any edit section can move as long as it doesn't cross an equality. 796 | * @param {Array} diffs Array of diff tuples. 797 | * @param {boolean} fix_unicode Whether to normalize to a unicode-correct diff 798 | */ 799 | function diff_cleanupMerge(diffs, fix_unicode) { 800 | diffs.push([DIFF_EQUAL, ""]); // Add a dummy entry at the end. 801 | var pointer = 0; 802 | var count_delete = 0; 803 | var count_insert = 0; 804 | var text_delete = ""; 805 | var text_insert = ""; 806 | var commonlength; 807 | while (pointer < diffs.length) { 808 | if (pointer < diffs.length - 1 && !diffs[pointer][1]) { 809 | diffs.splice(pointer, 1); 810 | continue; 811 | } 812 | switch (diffs[pointer][0]) { 813 | case DIFF_INSERT: 814 | count_insert++; 815 | text_insert += diffs[pointer][1]; 816 | pointer++; 817 | break; 818 | case DIFF_DELETE: 819 | count_delete++; 820 | text_delete += diffs[pointer][1]; 821 | pointer++; 822 | break; 823 | case DIFF_EQUAL: 824 | var previous_equality = pointer - count_insert - count_delete - 1; 825 | if (fix_unicode) { 826 | // prevent splitting of unicode surrogate pairs. when fix_unicode is true, 827 | // we assume that the old and new text in the diff are complete and correct 828 | // unicode-encoded JS strings, but the tuple boundaries may fall between 829 | // surrogate pairs. we fix this by shaving off stray surrogates from the end 830 | // of the previous equality and the beginning of this equality. this may create 831 | // empty equalities or a common prefix or suffix. for example, if AB and AC are 832 | // emojis, `[[0, 'A'], [-1, 'BA'], [0, 'C']]` would turn into deleting 'ABAC' and 833 | // inserting 'AC', and then the common suffix 'AC' will be eliminated. in this 834 | // particular case, both equalities go away, we absorb any previous inequalities, 835 | // and we keep scanning for the next equality before rewriting the tuples. 836 | if ( 837 | previous_equality >= 0 && 838 | ends_with_pair_start(diffs[previous_equality][1]) 839 | ) { 840 | var stray = diffs[previous_equality][1].slice(-1); 841 | diffs[previous_equality][1] = diffs[previous_equality][1].slice( 842 | 0, 843 | -1 844 | ); 845 | text_delete = stray + text_delete; 846 | text_insert = stray + text_insert; 847 | if (!diffs[previous_equality][1]) { 848 | // emptied out previous equality, so delete it and include previous delete/insert 849 | diffs.splice(previous_equality, 1); 850 | pointer--; 851 | var k = previous_equality - 1; 852 | if (diffs[k] && diffs[k][0] === DIFF_INSERT) { 853 | count_insert++; 854 | text_insert = diffs[k][1] + text_insert; 855 | k--; 856 | } 857 | if (diffs[k] && diffs[k][0] === DIFF_DELETE) { 858 | count_delete++; 859 | text_delete = diffs[k][1] + text_delete; 860 | k--; 861 | } 862 | previous_equality = k; 863 | } 864 | } 865 | if (starts_with_pair_end(diffs[pointer][1])) { 866 | var stray = diffs[pointer][1].charAt(0); 867 | diffs[pointer][1] = diffs[pointer][1].slice(1); 868 | text_delete += stray; 869 | text_insert += stray; 870 | } 871 | } 872 | if (pointer < diffs.length - 1 && !diffs[pointer][1]) { 873 | // for empty equality not at end, wait for next equality 874 | diffs.splice(pointer, 1); 875 | break; 876 | } 877 | if (text_delete.length > 0 || text_insert.length > 0) { 878 | // note that diff_commonPrefix and diff_commonSuffix are unicode-aware 879 | if (text_delete.length > 0 && text_insert.length > 0) { 880 | // Factor out any common prefixes. 881 | commonlength = diff_commonPrefix(text_insert, text_delete); 882 | if (commonlength !== 0) { 883 | if (previous_equality >= 0) { 884 | diffs[previous_equality][1] += text_insert.substring( 885 | 0, 886 | commonlength 887 | ); 888 | } else { 889 | diffs.splice(0, 0, [ 890 | DIFF_EQUAL, 891 | text_insert.substring(0, commonlength), 892 | ]); 893 | pointer++; 894 | } 895 | text_insert = text_insert.substring(commonlength); 896 | text_delete = text_delete.substring(commonlength); 897 | } 898 | // Factor out any common suffixes. 899 | commonlength = diff_commonSuffix(text_insert, text_delete); 900 | if (commonlength !== 0) { 901 | diffs[pointer][1] = 902 | text_insert.substring(text_insert.length - commonlength) + 903 | diffs[pointer][1]; 904 | text_insert = text_insert.substring( 905 | 0, 906 | text_insert.length - commonlength 907 | ); 908 | text_delete = text_delete.substring( 909 | 0, 910 | text_delete.length - commonlength 911 | ); 912 | } 913 | } 914 | // Delete the offending records and add the merged ones. 915 | var n = count_insert + count_delete; 916 | if (text_delete.length === 0 && text_insert.length === 0) { 917 | diffs.splice(pointer - n, n); 918 | pointer = pointer - n; 919 | } else if (text_delete.length === 0) { 920 | diffs.splice(pointer - n, n, [DIFF_INSERT, text_insert]); 921 | pointer = pointer - n + 1; 922 | } else if (text_insert.length === 0) { 923 | diffs.splice(pointer - n, n, [DIFF_DELETE, text_delete]); 924 | pointer = pointer - n + 1; 925 | } else { 926 | diffs.splice( 927 | pointer - n, 928 | n, 929 | [DIFF_DELETE, text_delete], 930 | [DIFF_INSERT, text_insert] 931 | ); 932 | pointer = pointer - n + 2; 933 | } 934 | } 935 | if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) { 936 | // Merge this equality with the previous one. 937 | diffs[pointer - 1][1] += diffs[pointer][1]; 938 | diffs.splice(pointer, 1); 939 | } else { 940 | pointer++; 941 | } 942 | count_insert = 0; 943 | count_delete = 0; 944 | text_delete = ""; 945 | text_insert = ""; 946 | break; 947 | } 948 | } 949 | if (diffs[diffs.length - 1][1] === "") { 950 | diffs.pop(); // Remove the dummy entry at the end. 951 | } 952 | 953 | // Second pass: look for single edits surrounded on both sides by equalities 954 | // which can be shifted sideways to eliminate an equality. 955 | // e.g: ABAC -> ABAC 956 | var changes = false; 957 | pointer = 1; 958 | // Intentionally ignore the first and last element (don't need checking). 959 | while (pointer < diffs.length - 1) { 960 | if ( 961 | diffs[pointer - 1][0] === DIFF_EQUAL && 962 | diffs[pointer + 1][0] === DIFF_EQUAL 963 | ) { 964 | // This is a single edit surrounded by equalities. 965 | if ( 966 | diffs[pointer][1].substring( 967 | diffs[pointer][1].length - diffs[pointer - 1][1].length 968 | ) === diffs[pointer - 1][1] 969 | ) { 970 | // Shift the edit over the previous equality. 971 | diffs[pointer][1] = 972 | diffs[pointer - 1][1] + 973 | diffs[pointer][1].substring( 974 | 0, 975 | diffs[pointer][1].length - diffs[pointer - 1][1].length 976 | ); 977 | diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; 978 | diffs.splice(pointer - 1, 1); 979 | changes = true; 980 | } else if ( 981 | diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) == 982 | diffs[pointer + 1][1] 983 | ) { 984 | // Shift the edit over the next equality. 985 | diffs[pointer - 1][1] += diffs[pointer + 1][1]; 986 | diffs[pointer][1] = 987 | diffs[pointer][1].substring(diffs[pointer + 1][1].length) + 988 | diffs[pointer + 1][1]; 989 | diffs.splice(pointer + 1, 1); 990 | changes = true; 991 | } 992 | } 993 | pointer++; 994 | } 995 | // If shifts were made, the diff needs reordering and another shift sweep. 996 | if (changes) { 997 | diff_cleanupMerge(diffs, fix_unicode); 998 | } 999 | } 1000 | 1001 | function is_surrogate_pair_start(charCode) { 1002 | return charCode >= 0xd800 && charCode <= 0xdbff; 1003 | } 1004 | 1005 | function is_surrogate_pair_end(charCode) { 1006 | return charCode >= 0xdc00 && charCode <= 0xdfff; 1007 | } 1008 | 1009 | function starts_with_pair_end(str) { 1010 | return is_surrogate_pair_end(str.charCodeAt(0)); 1011 | } 1012 | 1013 | function ends_with_pair_start(str) { 1014 | return is_surrogate_pair_start(str.charCodeAt(str.length - 1)); 1015 | } 1016 | 1017 | function remove_empty_tuples(tuples) { 1018 | var ret = []; 1019 | for (var i = 0; i < tuples.length; i++) { 1020 | if (tuples[i][1].length > 0) { 1021 | ret.push(tuples[i]); 1022 | } 1023 | } 1024 | return ret; 1025 | } 1026 | 1027 | function make_edit_splice(before, oldMiddle, newMiddle, after) { 1028 | if (ends_with_pair_start(before) || starts_with_pair_end(after)) { 1029 | return null; 1030 | } 1031 | return remove_empty_tuples([ 1032 | [DIFF_EQUAL, before], 1033 | [DIFF_DELETE, oldMiddle], 1034 | [DIFF_INSERT, newMiddle], 1035 | [DIFF_EQUAL, after], 1036 | ]); 1037 | } 1038 | 1039 | function find_cursor_edit_diff(oldText, newText, cursor_pos) { 1040 | // note: this runs after equality check has ruled out exact equality 1041 | var oldRange = 1042 | typeof cursor_pos === "number" 1043 | ? { index: cursor_pos, length: 0 } 1044 | : cursor_pos.oldRange; 1045 | var newRange = typeof cursor_pos === "number" ? null : cursor_pos.newRange; 1046 | // take into account the old and new selection to generate the best diff 1047 | // possible for a text edit. for example, a text change from "xxx" to "xx" 1048 | // could be a delete or forwards-delete of any one of the x's, or the 1049 | // result of selecting two of the x's and typing "x". 1050 | var oldLength = oldText.length; 1051 | var newLength = newText.length; 1052 | if (oldRange.length === 0 && (newRange === null || newRange.length === 0)) { 1053 | // see if we have an insert or delete before or after cursor 1054 | var oldCursor = oldRange.index; 1055 | var oldBefore = oldText.slice(0, oldCursor); 1056 | var oldAfter = oldText.slice(oldCursor); 1057 | var maybeNewCursor = newRange ? newRange.index : null; 1058 | editBefore: { 1059 | // is this an insert or delete right before oldCursor? 1060 | var newCursor = oldCursor + newLength - oldLength; 1061 | if (maybeNewCursor !== null && maybeNewCursor !== newCursor) { 1062 | break editBefore; 1063 | } 1064 | if (newCursor < 0 || newCursor > newLength) { 1065 | break editBefore; 1066 | } 1067 | var newBefore = newText.slice(0, newCursor); 1068 | var newAfter = newText.slice(newCursor); 1069 | if (newAfter !== oldAfter) { 1070 | break editBefore; 1071 | } 1072 | var prefixLength = Math.min(oldCursor, newCursor); 1073 | var oldPrefix = oldBefore.slice(0, prefixLength); 1074 | var newPrefix = newBefore.slice(0, prefixLength); 1075 | if (oldPrefix !== newPrefix) { 1076 | break editBefore; 1077 | } 1078 | var oldMiddle = oldBefore.slice(prefixLength); 1079 | var newMiddle = newBefore.slice(prefixLength); 1080 | return make_edit_splice(oldPrefix, oldMiddle, newMiddle, oldAfter); 1081 | } 1082 | editAfter: { 1083 | // is this an insert or delete right after oldCursor? 1084 | if (maybeNewCursor !== null && maybeNewCursor !== oldCursor) { 1085 | break editAfter; 1086 | } 1087 | var cursor = oldCursor; 1088 | var newBefore = newText.slice(0, cursor); 1089 | var newAfter = newText.slice(cursor); 1090 | if (newBefore !== oldBefore) { 1091 | break editAfter; 1092 | } 1093 | var suffixLength = Math.min(oldLength - cursor, newLength - cursor); 1094 | var oldSuffix = oldAfter.slice(oldAfter.length - suffixLength); 1095 | var newSuffix = newAfter.slice(newAfter.length - suffixLength); 1096 | if (oldSuffix !== newSuffix) { 1097 | break editAfter; 1098 | } 1099 | var oldMiddle = oldAfter.slice(0, oldAfter.length - suffixLength); 1100 | var newMiddle = newAfter.slice(0, newAfter.length - suffixLength); 1101 | return make_edit_splice(oldBefore, oldMiddle, newMiddle, oldSuffix); 1102 | } 1103 | } 1104 | if (oldRange.length > 0 && newRange && newRange.length === 0) { 1105 | replaceRange: { 1106 | // see if diff could be a splice of the old selection range 1107 | var oldPrefix = oldText.slice(0, oldRange.index); 1108 | var oldSuffix = oldText.slice(oldRange.index + oldRange.length); 1109 | var prefixLength = oldPrefix.length; 1110 | var suffixLength = oldSuffix.length; 1111 | if (newLength < prefixLength + suffixLength) { 1112 | break replaceRange; 1113 | } 1114 | var newPrefix = newText.slice(0, prefixLength); 1115 | var newSuffix = newText.slice(newLength - suffixLength); 1116 | if (oldPrefix !== newPrefix || oldSuffix !== newSuffix) { 1117 | break replaceRange; 1118 | } 1119 | var oldMiddle = oldText.slice(prefixLength, oldLength - suffixLength); 1120 | var newMiddle = newText.slice(prefixLength, newLength - suffixLength); 1121 | return make_edit_splice(oldPrefix, oldMiddle, newMiddle, oldSuffix); 1122 | } 1123 | } 1124 | 1125 | return null; 1126 | } 1127 | 1128 | function diff(text1, text2, cursor_pos, cleanup) { 1129 | // only pass fix_unicode=true at the top level, not when diff_main is 1130 | // recursively invoked 1131 | return diff_main(text1, text2, cursor_pos, cleanup, true); 1132 | } 1133 | 1134 | diff.INSERT = DIFF_INSERT; 1135 | diff.DELETE = DIFF_DELETE; 1136 | diff.EQUAL = DIFF_EQUAL; 1137 | 1138 | module.exports = diff; 1139 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-diff", 3 | "version": "1.2.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "fast-diff", 9 | "version": "1.2.0", 10 | "license": "Apache-2.0", 11 | "devDependencies": { 12 | "lodash": "~4.17.21", 13 | "nyc": "~15.1.0", 14 | "seedrandom": "~3.0.5" 15 | } 16 | }, 17 | "node_modules/@ampproject/remapping": { 18 | "version": "2.2.1", 19 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", 20 | "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", 21 | "dev": true, 22 | "dependencies": { 23 | "@jridgewell/gen-mapping": "^0.3.0", 24 | "@jridgewell/trace-mapping": "^0.3.9" 25 | }, 26 | "engines": { 27 | "node": ">=6.0.0" 28 | } 29 | }, 30 | "node_modules/@babel/code-frame": { 31 | "version": "7.21.4", 32 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", 33 | "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", 34 | "dev": true, 35 | "dependencies": { 36 | "@babel/highlight": "^7.18.6" 37 | }, 38 | "engines": { 39 | "node": ">=6.9.0" 40 | } 41 | }, 42 | "node_modules/@babel/compat-data": { 43 | "version": "7.21.4", 44 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", 45 | "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==", 46 | "dev": true, 47 | "engines": { 48 | "node": ">=6.9.0" 49 | } 50 | }, 51 | "node_modules/@babel/core": { 52 | "version": "7.21.4", 53 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", 54 | "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", 55 | "dev": true, 56 | "dependencies": { 57 | "@ampproject/remapping": "^2.2.0", 58 | "@babel/code-frame": "^7.21.4", 59 | "@babel/generator": "^7.21.4", 60 | "@babel/helper-compilation-targets": "^7.21.4", 61 | "@babel/helper-module-transforms": "^7.21.2", 62 | "@babel/helpers": "^7.21.0", 63 | "@babel/parser": "^7.21.4", 64 | "@babel/template": "^7.20.7", 65 | "@babel/traverse": "^7.21.4", 66 | "@babel/types": "^7.21.4", 67 | "convert-source-map": "^1.7.0", 68 | "debug": "^4.1.0", 69 | "gensync": "^1.0.0-beta.2", 70 | "json5": "^2.2.2", 71 | "semver": "^6.3.0" 72 | }, 73 | "engines": { 74 | "node": ">=6.9.0" 75 | }, 76 | "funding": { 77 | "type": "opencollective", 78 | "url": "https://opencollective.com/babel" 79 | } 80 | }, 81 | "node_modules/@babel/generator": { 82 | "version": "7.21.4", 83 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", 84 | "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", 85 | "dev": true, 86 | "dependencies": { 87 | "@babel/types": "^7.21.4", 88 | "@jridgewell/gen-mapping": "^0.3.2", 89 | "@jridgewell/trace-mapping": "^0.3.17", 90 | "jsesc": "^2.5.1" 91 | }, 92 | "engines": { 93 | "node": ">=6.9.0" 94 | } 95 | }, 96 | "node_modules/@babel/helper-compilation-targets": { 97 | "version": "7.21.4", 98 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", 99 | "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", 100 | "dev": true, 101 | "dependencies": { 102 | "@babel/compat-data": "^7.21.4", 103 | "@babel/helper-validator-option": "^7.21.0", 104 | "browserslist": "^4.21.3", 105 | "lru-cache": "^5.1.1", 106 | "semver": "^6.3.0" 107 | }, 108 | "engines": { 109 | "node": ">=6.9.0" 110 | }, 111 | "peerDependencies": { 112 | "@babel/core": "^7.0.0" 113 | } 114 | }, 115 | "node_modules/@babel/helper-environment-visitor": { 116 | "version": "7.18.9", 117 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", 118 | "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", 119 | "dev": true, 120 | "engines": { 121 | "node": ">=6.9.0" 122 | } 123 | }, 124 | "node_modules/@babel/helper-function-name": { 125 | "version": "7.21.0", 126 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", 127 | "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", 128 | "dev": true, 129 | "dependencies": { 130 | "@babel/template": "^7.20.7", 131 | "@babel/types": "^7.21.0" 132 | }, 133 | "engines": { 134 | "node": ">=6.9.0" 135 | } 136 | }, 137 | "node_modules/@babel/helper-hoist-variables": { 138 | "version": "7.18.6", 139 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", 140 | "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", 141 | "dev": true, 142 | "dependencies": { 143 | "@babel/types": "^7.18.6" 144 | }, 145 | "engines": { 146 | "node": ">=6.9.0" 147 | } 148 | }, 149 | "node_modules/@babel/helper-module-imports": { 150 | "version": "7.21.4", 151 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", 152 | "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", 153 | "dev": true, 154 | "dependencies": { 155 | "@babel/types": "^7.21.4" 156 | }, 157 | "engines": { 158 | "node": ">=6.9.0" 159 | } 160 | }, 161 | "node_modules/@babel/helper-module-transforms": { 162 | "version": "7.21.2", 163 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", 164 | "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", 165 | "dev": true, 166 | "dependencies": { 167 | "@babel/helper-environment-visitor": "^7.18.9", 168 | "@babel/helper-module-imports": "^7.18.6", 169 | "@babel/helper-simple-access": "^7.20.2", 170 | "@babel/helper-split-export-declaration": "^7.18.6", 171 | "@babel/helper-validator-identifier": "^7.19.1", 172 | "@babel/template": "^7.20.7", 173 | "@babel/traverse": "^7.21.2", 174 | "@babel/types": "^7.21.2" 175 | }, 176 | "engines": { 177 | "node": ">=6.9.0" 178 | } 179 | }, 180 | "node_modules/@babel/helper-simple-access": { 181 | "version": "7.20.2", 182 | "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", 183 | "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", 184 | "dev": true, 185 | "dependencies": { 186 | "@babel/types": "^7.20.2" 187 | }, 188 | "engines": { 189 | "node": ">=6.9.0" 190 | } 191 | }, 192 | "node_modules/@babel/helper-split-export-declaration": { 193 | "version": "7.18.6", 194 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", 195 | "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", 196 | "dev": true, 197 | "dependencies": { 198 | "@babel/types": "^7.18.6" 199 | }, 200 | "engines": { 201 | "node": ">=6.9.0" 202 | } 203 | }, 204 | "node_modules/@babel/helper-string-parser": { 205 | "version": "7.19.4", 206 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", 207 | "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", 208 | "dev": true, 209 | "engines": { 210 | "node": ">=6.9.0" 211 | } 212 | }, 213 | "node_modules/@babel/helper-validator-identifier": { 214 | "version": "7.19.1", 215 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", 216 | "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", 217 | "dev": true, 218 | "engines": { 219 | "node": ">=6.9.0" 220 | } 221 | }, 222 | "node_modules/@babel/helper-validator-option": { 223 | "version": "7.21.0", 224 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", 225 | "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", 226 | "dev": true, 227 | "engines": { 228 | "node": ">=6.9.0" 229 | } 230 | }, 231 | "node_modules/@babel/helpers": { 232 | "version": "7.21.0", 233 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", 234 | "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", 235 | "dev": true, 236 | "dependencies": { 237 | "@babel/template": "^7.20.7", 238 | "@babel/traverse": "^7.21.0", 239 | "@babel/types": "^7.21.0" 240 | }, 241 | "engines": { 242 | "node": ">=6.9.0" 243 | } 244 | }, 245 | "node_modules/@babel/highlight": { 246 | "version": "7.18.6", 247 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", 248 | "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", 249 | "dev": true, 250 | "dependencies": { 251 | "@babel/helper-validator-identifier": "^7.18.6", 252 | "chalk": "^2.0.0", 253 | "js-tokens": "^4.0.0" 254 | }, 255 | "engines": { 256 | "node": ">=6.9.0" 257 | } 258 | }, 259 | "node_modules/@babel/parser": { 260 | "version": "7.21.4", 261 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", 262 | "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", 263 | "dev": true, 264 | "bin": { 265 | "parser": "bin/babel-parser.js" 266 | }, 267 | "engines": { 268 | "node": ">=6.0.0" 269 | } 270 | }, 271 | "node_modules/@babel/template": { 272 | "version": "7.20.7", 273 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", 274 | "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", 275 | "dev": true, 276 | "dependencies": { 277 | "@babel/code-frame": "^7.18.6", 278 | "@babel/parser": "^7.20.7", 279 | "@babel/types": "^7.20.7" 280 | }, 281 | "engines": { 282 | "node": ">=6.9.0" 283 | } 284 | }, 285 | "node_modules/@babel/traverse": { 286 | "version": "7.21.4", 287 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", 288 | "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", 289 | "dev": true, 290 | "dependencies": { 291 | "@babel/code-frame": "^7.21.4", 292 | "@babel/generator": "^7.21.4", 293 | "@babel/helper-environment-visitor": "^7.18.9", 294 | "@babel/helper-function-name": "^7.21.0", 295 | "@babel/helper-hoist-variables": "^7.18.6", 296 | "@babel/helper-split-export-declaration": "^7.18.6", 297 | "@babel/parser": "^7.21.4", 298 | "@babel/types": "^7.21.4", 299 | "debug": "^4.1.0", 300 | "globals": "^11.1.0" 301 | }, 302 | "engines": { 303 | "node": ">=6.9.0" 304 | } 305 | }, 306 | "node_modules/@babel/types": { 307 | "version": "7.21.4", 308 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", 309 | "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", 310 | "dev": true, 311 | "dependencies": { 312 | "@babel/helper-string-parser": "^7.19.4", 313 | "@babel/helper-validator-identifier": "^7.19.1", 314 | "to-fast-properties": "^2.0.0" 315 | }, 316 | "engines": { 317 | "node": ">=6.9.0" 318 | } 319 | }, 320 | "node_modules/@istanbuljs/load-nyc-config": { 321 | "version": "1.1.0", 322 | "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", 323 | "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", 324 | "dev": true, 325 | "dependencies": { 326 | "camelcase": "^5.3.1", 327 | "find-up": "^4.1.0", 328 | "get-package-type": "^0.1.0", 329 | "js-yaml": "^3.13.1", 330 | "resolve-from": "^5.0.0" 331 | }, 332 | "engines": { 333 | "node": ">=8" 334 | } 335 | }, 336 | "node_modules/@istanbuljs/schema": { 337 | "version": "0.1.3", 338 | "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", 339 | "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", 340 | "dev": true, 341 | "engines": { 342 | "node": ">=8" 343 | } 344 | }, 345 | "node_modules/@jridgewell/gen-mapping": { 346 | "version": "0.3.3", 347 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", 348 | "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", 349 | "dev": true, 350 | "dependencies": { 351 | "@jridgewell/set-array": "^1.0.1", 352 | "@jridgewell/sourcemap-codec": "^1.4.10", 353 | "@jridgewell/trace-mapping": "^0.3.9" 354 | }, 355 | "engines": { 356 | "node": ">=6.0.0" 357 | } 358 | }, 359 | "node_modules/@jridgewell/resolve-uri": { 360 | "version": "3.1.0", 361 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 362 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 363 | "dev": true, 364 | "engines": { 365 | "node": ">=6.0.0" 366 | } 367 | }, 368 | "node_modules/@jridgewell/set-array": { 369 | "version": "1.1.2", 370 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 371 | "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", 372 | "dev": true, 373 | "engines": { 374 | "node": ">=6.0.0" 375 | } 376 | }, 377 | "node_modules/@jridgewell/sourcemap-codec": { 378 | "version": "1.4.15", 379 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 380 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 381 | "dev": true 382 | }, 383 | "node_modules/@jridgewell/trace-mapping": { 384 | "version": "0.3.18", 385 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", 386 | "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", 387 | "dev": true, 388 | "dependencies": { 389 | "@jridgewell/resolve-uri": "3.1.0", 390 | "@jridgewell/sourcemap-codec": "1.4.14" 391 | } 392 | }, 393 | "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { 394 | "version": "1.4.14", 395 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 396 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", 397 | "dev": true 398 | }, 399 | "node_modules/aggregate-error": { 400 | "version": "3.1.0", 401 | "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", 402 | "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", 403 | "dev": true, 404 | "dependencies": { 405 | "clean-stack": "^2.0.0", 406 | "indent-string": "^4.0.0" 407 | }, 408 | "engines": { 409 | "node": ">=8" 410 | } 411 | }, 412 | "node_modules/ansi-regex": { 413 | "version": "5.0.1", 414 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 415 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 416 | "dev": true, 417 | "engines": { 418 | "node": ">=8" 419 | } 420 | }, 421 | "node_modules/ansi-styles": { 422 | "version": "3.2.1", 423 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 424 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 425 | "dev": true, 426 | "dependencies": { 427 | "color-convert": "^1.9.0" 428 | }, 429 | "engines": { 430 | "node": ">=4" 431 | } 432 | }, 433 | "node_modules/append-transform": { 434 | "version": "2.0.0", 435 | "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", 436 | "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", 437 | "dev": true, 438 | "dependencies": { 439 | "default-require-extensions": "^3.0.0" 440 | }, 441 | "engines": { 442 | "node": ">=8" 443 | } 444 | }, 445 | "node_modules/archy": { 446 | "version": "1.0.0", 447 | "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", 448 | "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", 449 | "dev": true 450 | }, 451 | "node_modules/argparse": { 452 | "version": "1.0.10", 453 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 454 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 455 | "dev": true, 456 | "dependencies": { 457 | "sprintf-js": "~1.0.2" 458 | } 459 | }, 460 | "node_modules/balanced-match": { 461 | "version": "1.0.2", 462 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 463 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 464 | "dev": true 465 | }, 466 | "node_modules/brace-expansion": { 467 | "version": "1.1.11", 468 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 469 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 470 | "dev": true, 471 | "dependencies": { 472 | "balanced-match": "^1.0.0", 473 | "concat-map": "0.0.1" 474 | } 475 | }, 476 | "node_modules/browserslist": { 477 | "version": "4.21.5", 478 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", 479 | "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", 480 | "dev": true, 481 | "funding": [ 482 | { 483 | "type": "opencollective", 484 | "url": "https://opencollective.com/browserslist" 485 | }, 486 | { 487 | "type": "tidelift", 488 | "url": "https://tidelift.com/funding/github/npm/browserslist" 489 | } 490 | ], 491 | "dependencies": { 492 | "caniuse-lite": "^1.0.30001449", 493 | "electron-to-chromium": "^1.4.284", 494 | "node-releases": "^2.0.8", 495 | "update-browserslist-db": "^1.0.10" 496 | }, 497 | "bin": { 498 | "browserslist": "cli.js" 499 | }, 500 | "engines": { 501 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 502 | } 503 | }, 504 | "node_modules/caching-transform": { 505 | "version": "4.0.0", 506 | "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", 507 | "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", 508 | "dev": true, 509 | "dependencies": { 510 | "hasha": "^5.0.0", 511 | "make-dir": "^3.0.0", 512 | "package-hash": "^4.0.0", 513 | "write-file-atomic": "^3.0.0" 514 | }, 515 | "engines": { 516 | "node": ">=8" 517 | } 518 | }, 519 | "node_modules/camelcase": { 520 | "version": "5.3.1", 521 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 522 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 523 | "dev": true, 524 | "engines": { 525 | "node": ">=6" 526 | } 527 | }, 528 | "node_modules/caniuse-lite": { 529 | "version": "1.0.30001478", 530 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001478.tgz", 531 | "integrity": "sha512-gMhDyXGItTHipJj2ApIvR+iVB5hd0KP3svMWWXDvZOmjzJJassGLMfxRkQCSYgGd2gtdL/ReeiyvMSFD1Ss6Mw==", 532 | "dev": true, 533 | "funding": [ 534 | { 535 | "type": "opencollective", 536 | "url": "https://opencollective.com/browserslist" 537 | }, 538 | { 539 | "type": "tidelift", 540 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 541 | }, 542 | { 543 | "type": "github", 544 | "url": "https://github.com/sponsors/ai" 545 | } 546 | ] 547 | }, 548 | "node_modules/chalk": { 549 | "version": "2.4.2", 550 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 551 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 552 | "dev": true, 553 | "dependencies": { 554 | "ansi-styles": "^3.2.1", 555 | "escape-string-regexp": "^1.0.5", 556 | "supports-color": "^5.3.0" 557 | }, 558 | "engines": { 559 | "node": ">=4" 560 | } 561 | }, 562 | "node_modules/clean-stack": { 563 | "version": "2.2.0", 564 | "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", 565 | "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", 566 | "dev": true, 567 | "engines": { 568 | "node": ">=6" 569 | } 570 | }, 571 | "node_modules/cliui": { 572 | "version": "6.0.0", 573 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", 574 | "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", 575 | "dev": true, 576 | "dependencies": { 577 | "string-width": "^4.2.0", 578 | "strip-ansi": "^6.0.0", 579 | "wrap-ansi": "^6.2.0" 580 | } 581 | }, 582 | "node_modules/color-convert": { 583 | "version": "1.9.3", 584 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 585 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 586 | "dev": true, 587 | "dependencies": { 588 | "color-name": "1.1.3" 589 | } 590 | }, 591 | "node_modules/color-name": { 592 | "version": "1.1.3", 593 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 594 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", 595 | "dev": true 596 | }, 597 | "node_modules/commondir": { 598 | "version": "1.0.1", 599 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", 600 | "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", 601 | "dev": true 602 | }, 603 | "node_modules/concat-map": { 604 | "version": "0.0.1", 605 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 606 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 607 | "dev": true 608 | }, 609 | "node_modules/convert-source-map": { 610 | "version": "1.9.0", 611 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", 612 | "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", 613 | "dev": true 614 | }, 615 | "node_modules/cross-spawn": { 616 | "version": "7.0.3", 617 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 618 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 619 | "dev": true, 620 | "dependencies": { 621 | "path-key": "^3.1.0", 622 | "shebang-command": "^2.0.0", 623 | "which": "^2.0.1" 624 | }, 625 | "engines": { 626 | "node": ">= 8" 627 | } 628 | }, 629 | "node_modules/debug": { 630 | "version": "4.3.4", 631 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 632 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 633 | "dev": true, 634 | "dependencies": { 635 | "ms": "2.1.2" 636 | }, 637 | "engines": { 638 | "node": ">=6.0" 639 | }, 640 | "peerDependenciesMeta": { 641 | "supports-color": { 642 | "optional": true 643 | } 644 | } 645 | }, 646 | "node_modules/decamelize": { 647 | "version": "1.2.0", 648 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 649 | "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", 650 | "dev": true, 651 | "engines": { 652 | "node": ">=0.10.0" 653 | } 654 | }, 655 | "node_modules/default-require-extensions": { 656 | "version": "3.0.1", 657 | "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", 658 | "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", 659 | "dev": true, 660 | "dependencies": { 661 | "strip-bom": "^4.0.0" 662 | }, 663 | "engines": { 664 | "node": ">=8" 665 | }, 666 | "funding": { 667 | "url": "https://github.com/sponsors/sindresorhus" 668 | } 669 | }, 670 | "node_modules/electron-to-chromium": { 671 | "version": "1.4.365", 672 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.365.tgz", 673 | "integrity": "sha512-FRHZO+1tUNO4TOPXmlxetkoaIY8uwHzd1kKopK/Gx2SKn1L47wJXWD44wxP5CGRyyP98z/c8e1eBzJrgPeiBOg==", 674 | "dev": true 675 | }, 676 | "node_modules/emoji-regex": { 677 | "version": "8.0.0", 678 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 679 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 680 | "dev": true 681 | }, 682 | "node_modules/es6-error": { 683 | "version": "4.1.1", 684 | "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", 685 | "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", 686 | "dev": true 687 | }, 688 | "node_modules/escalade": { 689 | "version": "3.1.1", 690 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 691 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 692 | "dev": true, 693 | "engines": { 694 | "node": ">=6" 695 | } 696 | }, 697 | "node_modules/escape-string-regexp": { 698 | "version": "1.0.5", 699 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 700 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 701 | "dev": true, 702 | "engines": { 703 | "node": ">=0.8.0" 704 | } 705 | }, 706 | "node_modules/esprima": { 707 | "version": "4.0.1", 708 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 709 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 710 | "dev": true, 711 | "bin": { 712 | "esparse": "bin/esparse.js", 713 | "esvalidate": "bin/esvalidate.js" 714 | }, 715 | "engines": { 716 | "node": ">=4" 717 | } 718 | }, 719 | "node_modules/find-cache-dir": { 720 | "version": "3.3.2", 721 | "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", 722 | "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", 723 | "dev": true, 724 | "dependencies": { 725 | "commondir": "^1.0.1", 726 | "make-dir": "^3.0.2", 727 | "pkg-dir": "^4.1.0" 728 | }, 729 | "engines": { 730 | "node": ">=8" 731 | }, 732 | "funding": { 733 | "url": "https://github.com/avajs/find-cache-dir?sponsor=1" 734 | } 735 | }, 736 | "node_modules/find-up": { 737 | "version": "4.1.0", 738 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 739 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 740 | "dev": true, 741 | "dependencies": { 742 | "locate-path": "^5.0.0", 743 | "path-exists": "^4.0.0" 744 | }, 745 | "engines": { 746 | "node": ">=8" 747 | } 748 | }, 749 | "node_modules/foreground-child": { 750 | "version": "2.0.0", 751 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", 752 | "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", 753 | "dev": true, 754 | "dependencies": { 755 | "cross-spawn": "^7.0.0", 756 | "signal-exit": "^3.0.2" 757 | }, 758 | "engines": { 759 | "node": ">=8.0.0" 760 | } 761 | }, 762 | "node_modules/fromentries": { 763 | "version": "1.3.2", 764 | "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", 765 | "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", 766 | "dev": true, 767 | "funding": [ 768 | { 769 | "type": "github", 770 | "url": "https://github.com/sponsors/feross" 771 | }, 772 | { 773 | "type": "patreon", 774 | "url": "https://www.patreon.com/feross" 775 | }, 776 | { 777 | "type": "consulting", 778 | "url": "https://feross.org/support" 779 | } 780 | ] 781 | }, 782 | "node_modules/fs.realpath": { 783 | "version": "1.0.0", 784 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 785 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 786 | "dev": true 787 | }, 788 | "node_modules/gensync": { 789 | "version": "1.0.0-beta.2", 790 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 791 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 792 | "dev": true, 793 | "engines": { 794 | "node": ">=6.9.0" 795 | } 796 | }, 797 | "node_modules/get-caller-file": { 798 | "version": "2.0.5", 799 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 800 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 801 | "dev": true, 802 | "engines": { 803 | "node": "6.* || 8.* || >= 10.*" 804 | } 805 | }, 806 | "node_modules/get-package-type": { 807 | "version": "0.1.0", 808 | "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", 809 | "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", 810 | "dev": true, 811 | "engines": { 812 | "node": ">=8.0.0" 813 | } 814 | }, 815 | "node_modules/glob": { 816 | "version": "7.2.3", 817 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 818 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 819 | "dev": true, 820 | "dependencies": { 821 | "fs.realpath": "^1.0.0", 822 | "inflight": "^1.0.4", 823 | "inherits": "2", 824 | "minimatch": "^3.1.1", 825 | "once": "^1.3.0", 826 | "path-is-absolute": "^1.0.0" 827 | }, 828 | "engines": { 829 | "node": "*" 830 | }, 831 | "funding": { 832 | "url": "https://github.com/sponsors/isaacs" 833 | } 834 | }, 835 | "node_modules/globals": { 836 | "version": "11.12.0", 837 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 838 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 839 | "dev": true, 840 | "engines": { 841 | "node": ">=4" 842 | } 843 | }, 844 | "node_modules/graceful-fs": { 845 | "version": "4.2.11", 846 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 847 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 848 | "dev": true 849 | }, 850 | "node_modules/has-flag": { 851 | "version": "3.0.0", 852 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 853 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 854 | "dev": true, 855 | "engines": { 856 | "node": ">=4" 857 | } 858 | }, 859 | "node_modules/hasha": { 860 | "version": "5.2.2", 861 | "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", 862 | "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", 863 | "dev": true, 864 | "dependencies": { 865 | "is-stream": "^2.0.0", 866 | "type-fest": "^0.8.0" 867 | }, 868 | "engines": { 869 | "node": ">=8" 870 | }, 871 | "funding": { 872 | "url": "https://github.com/sponsors/sindresorhus" 873 | } 874 | }, 875 | "node_modules/html-escaper": { 876 | "version": "2.0.2", 877 | "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", 878 | "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", 879 | "dev": true 880 | }, 881 | "node_modules/imurmurhash": { 882 | "version": "0.1.4", 883 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 884 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 885 | "dev": true, 886 | "engines": { 887 | "node": ">=0.8.19" 888 | } 889 | }, 890 | "node_modules/indent-string": { 891 | "version": "4.0.0", 892 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", 893 | "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", 894 | "dev": true, 895 | "engines": { 896 | "node": ">=8" 897 | } 898 | }, 899 | "node_modules/inflight": { 900 | "version": "1.0.6", 901 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 902 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 903 | "dev": true, 904 | "dependencies": { 905 | "once": "^1.3.0", 906 | "wrappy": "1" 907 | } 908 | }, 909 | "node_modules/inherits": { 910 | "version": "2.0.4", 911 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 912 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 913 | "dev": true 914 | }, 915 | "node_modules/is-fullwidth-code-point": { 916 | "version": "3.0.0", 917 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 918 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 919 | "dev": true, 920 | "engines": { 921 | "node": ">=8" 922 | } 923 | }, 924 | "node_modules/is-stream": { 925 | "version": "2.0.1", 926 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 927 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", 928 | "dev": true, 929 | "engines": { 930 | "node": ">=8" 931 | }, 932 | "funding": { 933 | "url": "https://github.com/sponsors/sindresorhus" 934 | } 935 | }, 936 | "node_modules/is-typedarray": { 937 | "version": "1.0.0", 938 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 939 | "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", 940 | "dev": true 941 | }, 942 | "node_modules/is-windows": { 943 | "version": "1.0.2", 944 | "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", 945 | "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", 946 | "dev": true, 947 | "engines": { 948 | "node": ">=0.10.0" 949 | } 950 | }, 951 | "node_modules/isexe": { 952 | "version": "2.0.0", 953 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 954 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 955 | "dev": true 956 | }, 957 | "node_modules/istanbul-lib-coverage": { 958 | "version": "3.2.0", 959 | "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", 960 | "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", 961 | "dev": true, 962 | "engines": { 963 | "node": ">=8" 964 | } 965 | }, 966 | "node_modules/istanbul-lib-hook": { 967 | "version": "3.0.0", 968 | "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", 969 | "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", 970 | "dev": true, 971 | "dependencies": { 972 | "append-transform": "^2.0.0" 973 | }, 974 | "engines": { 975 | "node": ">=8" 976 | } 977 | }, 978 | "node_modules/istanbul-lib-instrument": { 979 | "version": "4.0.3", 980 | "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", 981 | "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", 982 | "dev": true, 983 | "dependencies": { 984 | "@babel/core": "^7.7.5", 985 | "@istanbuljs/schema": "^0.1.2", 986 | "istanbul-lib-coverage": "^3.0.0", 987 | "semver": "^6.3.0" 988 | }, 989 | "engines": { 990 | "node": ">=8" 991 | } 992 | }, 993 | "node_modules/istanbul-lib-processinfo": { 994 | "version": "2.0.3", 995 | "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", 996 | "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", 997 | "dev": true, 998 | "dependencies": { 999 | "archy": "^1.0.0", 1000 | "cross-spawn": "^7.0.3", 1001 | "istanbul-lib-coverage": "^3.2.0", 1002 | "p-map": "^3.0.0", 1003 | "rimraf": "^3.0.0", 1004 | "uuid": "^8.3.2" 1005 | }, 1006 | "engines": { 1007 | "node": ">=8" 1008 | } 1009 | }, 1010 | "node_modules/istanbul-lib-report": { 1011 | "version": "3.0.0", 1012 | "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", 1013 | "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", 1014 | "dev": true, 1015 | "dependencies": { 1016 | "istanbul-lib-coverage": "^3.0.0", 1017 | "make-dir": "^3.0.0", 1018 | "supports-color": "^7.1.0" 1019 | }, 1020 | "engines": { 1021 | "node": ">=8" 1022 | } 1023 | }, 1024 | "node_modules/istanbul-lib-report/node_modules/has-flag": { 1025 | "version": "4.0.0", 1026 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1027 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1028 | "dev": true, 1029 | "engines": { 1030 | "node": ">=8" 1031 | } 1032 | }, 1033 | "node_modules/istanbul-lib-report/node_modules/supports-color": { 1034 | "version": "7.2.0", 1035 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1036 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1037 | "dev": true, 1038 | "dependencies": { 1039 | "has-flag": "^4.0.0" 1040 | }, 1041 | "engines": { 1042 | "node": ">=8" 1043 | } 1044 | }, 1045 | "node_modules/istanbul-lib-source-maps": { 1046 | "version": "4.0.1", 1047 | "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", 1048 | "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", 1049 | "dev": true, 1050 | "dependencies": { 1051 | "debug": "^4.1.1", 1052 | "istanbul-lib-coverage": "^3.0.0", 1053 | "source-map": "^0.6.1" 1054 | }, 1055 | "engines": { 1056 | "node": ">=10" 1057 | } 1058 | }, 1059 | "node_modules/istanbul-reports": { 1060 | "version": "3.1.5", 1061 | "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", 1062 | "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", 1063 | "dev": true, 1064 | "dependencies": { 1065 | "html-escaper": "^2.0.0", 1066 | "istanbul-lib-report": "^3.0.0" 1067 | }, 1068 | "engines": { 1069 | "node": ">=8" 1070 | } 1071 | }, 1072 | "node_modules/js-tokens": { 1073 | "version": "4.0.0", 1074 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1075 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1076 | "dev": true 1077 | }, 1078 | "node_modules/js-yaml": { 1079 | "version": "3.14.1", 1080 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 1081 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 1082 | "dev": true, 1083 | "dependencies": { 1084 | "argparse": "^1.0.7", 1085 | "esprima": "^4.0.0" 1086 | }, 1087 | "bin": { 1088 | "js-yaml": "bin/js-yaml.js" 1089 | } 1090 | }, 1091 | "node_modules/jsesc": { 1092 | "version": "2.5.2", 1093 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 1094 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", 1095 | "dev": true, 1096 | "bin": { 1097 | "jsesc": "bin/jsesc" 1098 | }, 1099 | "engines": { 1100 | "node": ">=4" 1101 | } 1102 | }, 1103 | "node_modules/json5": { 1104 | "version": "2.2.3", 1105 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 1106 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 1107 | "dev": true, 1108 | "bin": { 1109 | "json5": "lib/cli.js" 1110 | }, 1111 | "engines": { 1112 | "node": ">=6" 1113 | } 1114 | }, 1115 | "node_modules/locate-path": { 1116 | "version": "5.0.0", 1117 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 1118 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 1119 | "dev": true, 1120 | "dependencies": { 1121 | "p-locate": "^4.1.0" 1122 | }, 1123 | "engines": { 1124 | "node": ">=8" 1125 | } 1126 | }, 1127 | "node_modules/lodash": { 1128 | "version": "4.17.21", 1129 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 1130 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 1131 | "dev": true 1132 | }, 1133 | "node_modules/lodash.flattendeep": { 1134 | "version": "4.4.0", 1135 | "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", 1136 | "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", 1137 | "dev": true 1138 | }, 1139 | "node_modules/lru-cache": { 1140 | "version": "5.1.1", 1141 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 1142 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 1143 | "dev": true, 1144 | "dependencies": { 1145 | "yallist": "^3.0.2" 1146 | } 1147 | }, 1148 | "node_modules/make-dir": { 1149 | "version": "3.1.0", 1150 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 1151 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 1152 | "dev": true, 1153 | "dependencies": { 1154 | "semver": "^6.0.0" 1155 | }, 1156 | "engines": { 1157 | "node": ">=8" 1158 | }, 1159 | "funding": { 1160 | "url": "https://github.com/sponsors/sindresorhus" 1161 | } 1162 | }, 1163 | "node_modules/minimatch": { 1164 | "version": "3.1.2", 1165 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1166 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1167 | "dev": true, 1168 | "dependencies": { 1169 | "brace-expansion": "^1.1.7" 1170 | }, 1171 | "engines": { 1172 | "node": "*" 1173 | } 1174 | }, 1175 | "node_modules/ms": { 1176 | "version": "2.1.2", 1177 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1178 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1179 | "dev": true 1180 | }, 1181 | "node_modules/node-preload": { 1182 | "version": "0.2.1", 1183 | "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", 1184 | "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", 1185 | "dev": true, 1186 | "dependencies": { 1187 | "process-on-spawn": "^1.0.0" 1188 | }, 1189 | "engines": { 1190 | "node": ">=8" 1191 | } 1192 | }, 1193 | "node_modules/node-releases": { 1194 | "version": "2.0.10", 1195 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", 1196 | "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", 1197 | "dev": true 1198 | }, 1199 | "node_modules/nyc": { 1200 | "version": "15.1.0", 1201 | "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", 1202 | "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", 1203 | "dev": true, 1204 | "dependencies": { 1205 | "@istanbuljs/load-nyc-config": "^1.0.0", 1206 | "@istanbuljs/schema": "^0.1.2", 1207 | "caching-transform": "^4.0.0", 1208 | "convert-source-map": "^1.7.0", 1209 | "decamelize": "^1.2.0", 1210 | "find-cache-dir": "^3.2.0", 1211 | "find-up": "^4.1.0", 1212 | "foreground-child": "^2.0.0", 1213 | "get-package-type": "^0.1.0", 1214 | "glob": "^7.1.6", 1215 | "istanbul-lib-coverage": "^3.0.0", 1216 | "istanbul-lib-hook": "^3.0.0", 1217 | "istanbul-lib-instrument": "^4.0.0", 1218 | "istanbul-lib-processinfo": "^2.0.2", 1219 | "istanbul-lib-report": "^3.0.0", 1220 | "istanbul-lib-source-maps": "^4.0.0", 1221 | "istanbul-reports": "^3.0.2", 1222 | "make-dir": "^3.0.0", 1223 | "node-preload": "^0.2.1", 1224 | "p-map": "^3.0.0", 1225 | "process-on-spawn": "^1.0.0", 1226 | "resolve-from": "^5.0.0", 1227 | "rimraf": "^3.0.0", 1228 | "signal-exit": "^3.0.2", 1229 | "spawn-wrap": "^2.0.0", 1230 | "test-exclude": "^6.0.0", 1231 | "yargs": "^15.0.2" 1232 | }, 1233 | "bin": { 1234 | "nyc": "bin/nyc.js" 1235 | }, 1236 | "engines": { 1237 | "node": ">=8.9" 1238 | } 1239 | }, 1240 | "node_modules/once": { 1241 | "version": "1.4.0", 1242 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1243 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1244 | "dev": true, 1245 | "dependencies": { 1246 | "wrappy": "1" 1247 | } 1248 | }, 1249 | "node_modules/p-limit": { 1250 | "version": "2.3.0", 1251 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 1252 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 1253 | "dev": true, 1254 | "dependencies": { 1255 | "p-try": "^2.0.0" 1256 | }, 1257 | "engines": { 1258 | "node": ">=6" 1259 | }, 1260 | "funding": { 1261 | "url": "https://github.com/sponsors/sindresorhus" 1262 | } 1263 | }, 1264 | "node_modules/p-locate": { 1265 | "version": "4.1.0", 1266 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 1267 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 1268 | "dev": true, 1269 | "dependencies": { 1270 | "p-limit": "^2.2.0" 1271 | }, 1272 | "engines": { 1273 | "node": ">=8" 1274 | } 1275 | }, 1276 | "node_modules/p-map": { 1277 | "version": "3.0.0", 1278 | "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", 1279 | "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", 1280 | "dev": true, 1281 | "dependencies": { 1282 | "aggregate-error": "^3.0.0" 1283 | }, 1284 | "engines": { 1285 | "node": ">=8" 1286 | } 1287 | }, 1288 | "node_modules/p-try": { 1289 | "version": "2.2.0", 1290 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 1291 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 1292 | "dev": true, 1293 | "engines": { 1294 | "node": ">=6" 1295 | } 1296 | }, 1297 | "node_modules/package-hash": { 1298 | "version": "4.0.0", 1299 | "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", 1300 | "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", 1301 | "dev": true, 1302 | "dependencies": { 1303 | "graceful-fs": "^4.1.15", 1304 | "hasha": "^5.0.0", 1305 | "lodash.flattendeep": "^4.4.0", 1306 | "release-zalgo": "^1.0.0" 1307 | }, 1308 | "engines": { 1309 | "node": ">=8" 1310 | } 1311 | }, 1312 | "node_modules/path-exists": { 1313 | "version": "4.0.0", 1314 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1315 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1316 | "dev": true, 1317 | "engines": { 1318 | "node": ">=8" 1319 | } 1320 | }, 1321 | "node_modules/path-is-absolute": { 1322 | "version": "1.0.1", 1323 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1324 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1325 | "dev": true, 1326 | "engines": { 1327 | "node": ">=0.10.0" 1328 | } 1329 | }, 1330 | "node_modules/path-key": { 1331 | "version": "3.1.1", 1332 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1333 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1334 | "dev": true, 1335 | "engines": { 1336 | "node": ">=8" 1337 | } 1338 | }, 1339 | "node_modules/picocolors": { 1340 | "version": "1.0.0", 1341 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 1342 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 1343 | "dev": true 1344 | }, 1345 | "node_modules/pkg-dir": { 1346 | "version": "4.2.0", 1347 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 1348 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 1349 | "dev": true, 1350 | "dependencies": { 1351 | "find-up": "^4.0.0" 1352 | }, 1353 | "engines": { 1354 | "node": ">=8" 1355 | } 1356 | }, 1357 | "node_modules/process-on-spawn": { 1358 | "version": "1.0.0", 1359 | "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", 1360 | "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", 1361 | "dev": true, 1362 | "dependencies": { 1363 | "fromentries": "^1.2.0" 1364 | }, 1365 | "engines": { 1366 | "node": ">=8" 1367 | } 1368 | }, 1369 | "node_modules/release-zalgo": { 1370 | "version": "1.0.0", 1371 | "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", 1372 | "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", 1373 | "dev": true, 1374 | "dependencies": { 1375 | "es6-error": "^4.0.1" 1376 | }, 1377 | "engines": { 1378 | "node": ">=4" 1379 | } 1380 | }, 1381 | "node_modules/require-directory": { 1382 | "version": "2.1.1", 1383 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 1384 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 1385 | "dev": true, 1386 | "engines": { 1387 | "node": ">=0.10.0" 1388 | } 1389 | }, 1390 | "node_modules/require-main-filename": { 1391 | "version": "2.0.0", 1392 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 1393 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 1394 | "dev": true 1395 | }, 1396 | "node_modules/resolve-from": { 1397 | "version": "5.0.0", 1398 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", 1399 | "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", 1400 | "dev": true, 1401 | "engines": { 1402 | "node": ">=8" 1403 | } 1404 | }, 1405 | "node_modules/rimraf": { 1406 | "version": "3.0.2", 1407 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1408 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1409 | "dev": true, 1410 | "dependencies": { 1411 | "glob": "^7.1.3" 1412 | }, 1413 | "bin": { 1414 | "rimraf": "bin.js" 1415 | }, 1416 | "funding": { 1417 | "url": "https://github.com/sponsors/isaacs" 1418 | } 1419 | }, 1420 | "node_modules/seedrandom": { 1421 | "version": "3.0.5", 1422 | "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", 1423 | "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", 1424 | "dev": true 1425 | }, 1426 | "node_modules/semver": { 1427 | "version": "6.3.0", 1428 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 1429 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 1430 | "dev": true, 1431 | "bin": { 1432 | "semver": "bin/semver.js" 1433 | } 1434 | }, 1435 | "node_modules/set-blocking": { 1436 | "version": "2.0.0", 1437 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1438 | "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", 1439 | "dev": true 1440 | }, 1441 | "node_modules/shebang-command": { 1442 | "version": "2.0.0", 1443 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1444 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1445 | "dev": true, 1446 | "dependencies": { 1447 | "shebang-regex": "^3.0.0" 1448 | }, 1449 | "engines": { 1450 | "node": ">=8" 1451 | } 1452 | }, 1453 | "node_modules/shebang-regex": { 1454 | "version": "3.0.0", 1455 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1456 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1457 | "dev": true, 1458 | "engines": { 1459 | "node": ">=8" 1460 | } 1461 | }, 1462 | "node_modules/signal-exit": { 1463 | "version": "3.0.7", 1464 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 1465 | "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", 1466 | "dev": true 1467 | }, 1468 | "node_modules/source-map": { 1469 | "version": "0.6.1", 1470 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1471 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1472 | "dev": true, 1473 | "engines": { 1474 | "node": ">=0.10.0" 1475 | } 1476 | }, 1477 | "node_modules/spawn-wrap": { 1478 | "version": "2.0.0", 1479 | "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", 1480 | "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", 1481 | "dev": true, 1482 | "dependencies": { 1483 | "foreground-child": "^2.0.0", 1484 | "is-windows": "^1.0.2", 1485 | "make-dir": "^3.0.0", 1486 | "rimraf": "^3.0.0", 1487 | "signal-exit": "^3.0.2", 1488 | "which": "^2.0.1" 1489 | }, 1490 | "engines": { 1491 | "node": ">=8" 1492 | } 1493 | }, 1494 | "node_modules/sprintf-js": { 1495 | "version": "1.0.3", 1496 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1497 | "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", 1498 | "dev": true 1499 | }, 1500 | "node_modules/string-width": { 1501 | "version": "4.2.3", 1502 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1503 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1504 | "dev": true, 1505 | "dependencies": { 1506 | "emoji-regex": "^8.0.0", 1507 | "is-fullwidth-code-point": "^3.0.0", 1508 | "strip-ansi": "^6.0.1" 1509 | }, 1510 | "engines": { 1511 | "node": ">=8" 1512 | } 1513 | }, 1514 | "node_modules/strip-ansi": { 1515 | "version": "6.0.1", 1516 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1517 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1518 | "dev": true, 1519 | "dependencies": { 1520 | "ansi-regex": "^5.0.1" 1521 | }, 1522 | "engines": { 1523 | "node": ">=8" 1524 | } 1525 | }, 1526 | "node_modules/strip-bom": { 1527 | "version": "4.0.0", 1528 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", 1529 | "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", 1530 | "dev": true, 1531 | "engines": { 1532 | "node": ">=8" 1533 | } 1534 | }, 1535 | "node_modules/supports-color": { 1536 | "version": "5.5.0", 1537 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1538 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1539 | "dev": true, 1540 | "dependencies": { 1541 | "has-flag": "^3.0.0" 1542 | }, 1543 | "engines": { 1544 | "node": ">=4" 1545 | } 1546 | }, 1547 | "node_modules/test-exclude": { 1548 | "version": "6.0.0", 1549 | "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", 1550 | "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", 1551 | "dev": true, 1552 | "dependencies": { 1553 | "@istanbuljs/schema": "^0.1.2", 1554 | "glob": "^7.1.4", 1555 | "minimatch": "^3.0.4" 1556 | }, 1557 | "engines": { 1558 | "node": ">=8" 1559 | } 1560 | }, 1561 | "node_modules/to-fast-properties": { 1562 | "version": "2.0.0", 1563 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 1564 | "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", 1565 | "dev": true, 1566 | "engines": { 1567 | "node": ">=4" 1568 | } 1569 | }, 1570 | "node_modules/type-fest": { 1571 | "version": "0.8.1", 1572 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", 1573 | "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", 1574 | "dev": true, 1575 | "engines": { 1576 | "node": ">=8" 1577 | } 1578 | }, 1579 | "node_modules/typedarray-to-buffer": { 1580 | "version": "3.1.5", 1581 | "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", 1582 | "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", 1583 | "dev": true, 1584 | "dependencies": { 1585 | "is-typedarray": "^1.0.0" 1586 | } 1587 | }, 1588 | "node_modules/update-browserslist-db": { 1589 | "version": "1.0.10", 1590 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", 1591 | "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", 1592 | "dev": true, 1593 | "funding": [ 1594 | { 1595 | "type": "opencollective", 1596 | "url": "https://opencollective.com/browserslist" 1597 | }, 1598 | { 1599 | "type": "tidelift", 1600 | "url": "https://tidelift.com/funding/github/npm/browserslist" 1601 | } 1602 | ], 1603 | "dependencies": { 1604 | "escalade": "^3.1.1", 1605 | "picocolors": "^1.0.0" 1606 | }, 1607 | "bin": { 1608 | "browserslist-lint": "cli.js" 1609 | }, 1610 | "peerDependencies": { 1611 | "browserslist": ">= 4.21.0" 1612 | } 1613 | }, 1614 | "node_modules/uuid": { 1615 | "version": "8.3.2", 1616 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 1617 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", 1618 | "dev": true, 1619 | "bin": { 1620 | "uuid": "dist/bin/uuid" 1621 | } 1622 | }, 1623 | "node_modules/which": { 1624 | "version": "2.0.2", 1625 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1626 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1627 | "dev": true, 1628 | "dependencies": { 1629 | "isexe": "^2.0.0" 1630 | }, 1631 | "bin": { 1632 | "node-which": "bin/node-which" 1633 | }, 1634 | "engines": { 1635 | "node": ">= 8" 1636 | } 1637 | }, 1638 | "node_modules/which-module": { 1639 | "version": "2.0.0", 1640 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 1641 | "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", 1642 | "dev": true 1643 | }, 1644 | "node_modules/wrap-ansi": { 1645 | "version": "6.2.0", 1646 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", 1647 | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", 1648 | "dev": true, 1649 | "dependencies": { 1650 | "ansi-styles": "^4.0.0", 1651 | "string-width": "^4.1.0", 1652 | "strip-ansi": "^6.0.0" 1653 | }, 1654 | "engines": { 1655 | "node": ">=8" 1656 | } 1657 | }, 1658 | "node_modules/wrap-ansi/node_modules/ansi-styles": { 1659 | "version": "4.3.0", 1660 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1661 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1662 | "dev": true, 1663 | "dependencies": { 1664 | "color-convert": "^2.0.1" 1665 | }, 1666 | "engines": { 1667 | "node": ">=8" 1668 | }, 1669 | "funding": { 1670 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1671 | } 1672 | }, 1673 | "node_modules/wrap-ansi/node_modules/color-convert": { 1674 | "version": "2.0.1", 1675 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1676 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1677 | "dev": true, 1678 | "dependencies": { 1679 | "color-name": "~1.1.4" 1680 | }, 1681 | "engines": { 1682 | "node": ">=7.0.0" 1683 | } 1684 | }, 1685 | "node_modules/wrap-ansi/node_modules/color-name": { 1686 | "version": "1.1.4", 1687 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1688 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1689 | "dev": true 1690 | }, 1691 | "node_modules/wrappy": { 1692 | "version": "1.0.2", 1693 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1694 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1695 | "dev": true 1696 | }, 1697 | "node_modules/write-file-atomic": { 1698 | "version": "3.0.3", 1699 | "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", 1700 | "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", 1701 | "dev": true, 1702 | "dependencies": { 1703 | "imurmurhash": "^0.1.4", 1704 | "is-typedarray": "^1.0.0", 1705 | "signal-exit": "^3.0.2", 1706 | "typedarray-to-buffer": "^3.1.5" 1707 | } 1708 | }, 1709 | "node_modules/y18n": { 1710 | "version": "4.0.3", 1711 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", 1712 | "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", 1713 | "dev": true 1714 | }, 1715 | "node_modules/yallist": { 1716 | "version": "3.1.1", 1717 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 1718 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 1719 | "dev": true 1720 | }, 1721 | "node_modules/yargs": { 1722 | "version": "15.4.1", 1723 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", 1724 | "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", 1725 | "dev": true, 1726 | "dependencies": { 1727 | "cliui": "^6.0.0", 1728 | "decamelize": "^1.2.0", 1729 | "find-up": "^4.1.0", 1730 | "get-caller-file": "^2.0.1", 1731 | "require-directory": "^2.1.1", 1732 | "require-main-filename": "^2.0.0", 1733 | "set-blocking": "^2.0.0", 1734 | "string-width": "^4.2.0", 1735 | "which-module": "^2.0.0", 1736 | "y18n": "^4.0.0", 1737 | "yargs-parser": "^18.1.2" 1738 | }, 1739 | "engines": { 1740 | "node": ">=8" 1741 | } 1742 | }, 1743 | "node_modules/yargs-parser": { 1744 | "version": "18.1.3", 1745 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", 1746 | "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", 1747 | "dev": true, 1748 | "dependencies": { 1749 | "camelcase": "^5.0.0", 1750 | "decamelize": "^1.2.0" 1751 | }, 1752 | "engines": { 1753 | "node": ">=6" 1754 | } 1755 | } 1756 | } 1757 | } 1758 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-diff", 3 | "version": "1.3.0", 4 | "description": "Fast Javascript text diff", 5 | "author": "Jason Chen ", 6 | "main": "diff.js", 7 | "types": "diff.d.ts", 8 | "files": [ 9 | "diff.d.ts" 10 | ], 11 | "devDependencies": { 12 | "lodash": "~4.17.21", 13 | "nyc": "~15.1.0", 14 | "seedrandom": "~3.0.5" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/jhchen/fast-diff" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/jhchen/fast-diff/issues" 22 | }, 23 | "scripts": { 24 | "test": "nyc node test.js" 25 | }, 26 | "license": "Apache-2.0", 27 | "keywords": [ 28 | "diff" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var seedrandom = require("seedrandom"); 3 | var diff = require("./diff.js"); 4 | 5 | var ITERATIONS = 10000; 6 | var ALPHABET = "GATTACA"; 7 | var LENGTH = 100; 8 | var EMOJI_MAX_LENGTH = 50; 9 | 10 | var seed = Math.floor(Math.random() * 10000); 11 | var random = seedrandom(seed); 12 | 13 | console.log("Running regression tests..."); 14 | [ 15 | ["GAATAAAAAAAGATTAACAT", "AAAAACTTGTAATTAACAAC"], 16 | ["🔘🤘🔗🔗", "🔗🤗🤗__🤗🤘🤘🤗🔗🤘🔗"], 17 | ["🔗🤗🤗__🤗🤘🤘🤗🔗🤘🔗", "🤗🤘🔘"], 18 | ["🤘🤘🔘🔘_🔘🔗🤘🤗🤗__🔗🤘", "🤘🔘🤘🔗🤘🤘🔗🤗🤘🔘🔘"], 19 | [ 20 | "🤗🤘🤗🔘🤘🔘🤗_🤗🔗🤘🤗_🤘🔗🤗🤘🔗🤘🤘🤘🔗🤗🔗🔗🔗🤗_🤘🔗🤗🤗🔘🤗🤗🤘🤗", 21 | "_🤗🤘_🤘🤘🔘🤗🔘🤘_🔘🤗🔗🔘🔗🤘🔗🤘🤗🔗🔗🔗🤘🔘_🤗🤘🤘🤘__🤘_🔘🤘🤘_🔗🤘🔘", 22 | ], 23 | ["🔗🤘🤗🔘🔘🤗", "🤘🤘🤘🤗🔘🔗🔗"], 24 | ["🔘_🔗🔗🔗🤗🔗", "🤘🤗🔗🤗_🤘🔘_"], 25 | ].forEach(function (data) { 26 | var result = diff(data[0], data[1]); 27 | applyDiff(result, data[0], data[1]); 28 | }); 29 | 30 | console.log( 31 | "Running computing " + ITERATIONS + " diffs with seed " + seed + "..." 32 | ); 33 | 34 | console.log("Generating strings..."); 35 | var strings = []; 36 | for (var i = 0; i <= ITERATIONS; ++i) { 37 | var chars = []; 38 | for (var l = 0; l < LENGTH; ++l) { 39 | var letter = ALPHABET.substr(Math.floor(random() * ALPHABET.length), 1); 40 | chars.push(letter); 41 | } 42 | strings.push(chars.join("")); 43 | } 44 | 45 | console.log("Running fuzz tests *without* cursor information..."); 46 | for (var i = 0; i < ITERATIONS; ++i) { 47 | var result = diff(strings[i], strings[i + 1]); 48 | applyDiff(result, strings[i], strings[i + 1]); 49 | } 50 | 51 | console.log("Running fuzz tests *with* cursor information"); 52 | for (var i = 0; i < ITERATIONS; ++i) { 53 | var cursor_pos = Math.floor(random() * strings[i].length + 1); 54 | var diffs = diff(strings[i], strings[i + 1], cursor_pos); 55 | applyDiff(diffs, strings[i], strings[i + 1]); 56 | } 57 | 58 | function parseDiff(str) { 59 | if (!str) { 60 | return []; 61 | } 62 | return str.split(/(?=[+\-=])/).map(function (piece) { 63 | var symbol = piece.charAt(0); 64 | var text = piece.slice(1); 65 | return [ 66 | symbol === "+" ? diff.INSERT : symbol === "-" ? diff.DELETE : diff.EQUAL, 67 | text, 68 | ]; 69 | }); 70 | } 71 | 72 | console.log("Running cursor tests"); 73 | [ 74 | ["", 0, "", null, ""], 75 | 76 | ["", 0, "a", null, "+a"], 77 | ["a", 0, "aa", null, "+a=a"], 78 | ["a", 1, "aa", null, "=a+a"], 79 | ["aa", 0, "aaa", null, "+a=aa"], 80 | ["aa", 1, "aaa", null, "=a+a=a"], 81 | ["aa", 2, "aaa", null, "=aa+a"], 82 | ["aaa", 0, "aaaa", null, "+a=aaa"], 83 | ["aaa", 1, "aaaa", null, "=a+a=aa"], 84 | ["aaa", 2, "aaaa", null, "=aa+a=a"], 85 | ["aaa", 3, "aaaa", null, "=aaa+a"], 86 | 87 | ["a", 0, "", null, "-a"], 88 | ["a", 1, "", null, "-a"], 89 | ["aa", 0, "a", null, "-a=a"], 90 | ["aa", 1, "a", null, "-a=a"], 91 | ["aa", 2, "a", null, "=a-a"], 92 | ["aaa", 0, "aa", null, "-a=aa"], 93 | ["aaa", 1, "aa", null, "-a=aa"], 94 | ["aaa", 2, "aa", null, "=a-a=a"], 95 | ["aaa", 3, "aa", null, "=aa-a"], 96 | 97 | ["", 0, "", 0, ""], 98 | 99 | ["", 0, "a", 1, "+a"], 100 | ["a", 0, "aa", 1, "+a=a"], 101 | ["a", 1, "aa", 2, "=a+a"], 102 | ["aa", 0, "aaa", 1, "+a=aa"], 103 | ["aa", 1, "aaa", 2, "=a+a=a"], 104 | ["aa", 2, "aaa", 3, "=aa+a"], 105 | ["aaa", 0, "aaaa", 1, "+a=aaa"], 106 | ["aaa", 1, "aaaa", 2, "=a+a=aa"], 107 | ["aaa", 2, "aaaa", 3, "=aa+a=a"], 108 | ["aaa", 3, "aaaa", 4, "=aaa+a"], 109 | 110 | ["a", 1, "", 0, "-a"], 111 | ["aa", 1, "a", 0, "-a=a"], 112 | ["aa", 2, "a", 1, "=a-a"], 113 | ["aaa", 1, "aa", 0, "-a=aa"], 114 | ["aaa", 2, "aa", 1, "=a-a=a"], 115 | ["aaa", 3, "aa", 2, "=aa-a"], 116 | 117 | ["a", 1, "", 0, "-a"], 118 | ["aa", 1, "a", 0, "-a=a"], 119 | ["aa", 2, "a", 1, "=a-a"], 120 | ["aaa", 1, "aa", 0, "-a=aa"], 121 | ["aaa", 2, "aa", 1, "=a-a=a"], 122 | ["aaa", 3, "aa", 2, "=aa-a"], 123 | 124 | // neither edit before nor edit after 125 | ["aaa", 3, "aa", 0, "=aa-a"], 126 | 127 | // unmatched replace operations 128 | ["12345", [1, 2], "15", 1, "=1-234=5"], // unmatched length 129 | ["12345", [1, 2], "a545", 1, "-123+a5=45"], // unmatched prefix 130 | 131 | // forward-delete 132 | ["a", 0, "", 0, "-a"], 133 | ["aa", 0, "a", 0, "-a=a"], 134 | ["aa", 1, "a", 1, "=a-a"], 135 | ["aaa", 0, "aa", 0, "-a=aa"], 136 | ["aaa", 1, "aa", 1, "=a-a=a"], 137 | ["aaa", 2, "aa", 2, "=aa-a"], 138 | 139 | ["bob", 0, "bobob", null, "+bo=bob"], 140 | ["bob", 1, "bobob", null, "=b+ob=ob"], 141 | ["bob", 2, "bobob", null, "=bo+bo=b"], 142 | ["bob", 3, "bobob", null, "=bob+ob"], 143 | ["bob", 0, "bobob", 2, "+bo=bob"], 144 | ["bob", 1, "bobob", 3, "=b+ob=ob"], 145 | ["bob", 2, "bobob", 4, "=bo+bo=b"], 146 | ["bob", 3, "bobob", 5, "=bob+ob"], 147 | ["bobob", 2, "bob", null, "-bo=bob"], 148 | ["bobob", 3, "bob", null, "=b-ob=ob"], 149 | ["bobob", 4, "bob", null, "=bo-bo=b"], 150 | ["bobob", 5, "bob", null, "=bob-ob"], 151 | ["bobob", 2, "bob", 0, "-bo=bob"], 152 | ["bobob", 3, "bob", 1, "=b-ob=ob"], 153 | ["bobob", 4, "bob", 2, "=bo-bo=b"], 154 | ["bobob", 5, "bob", 3, "=bob-ob"], 155 | 156 | ["bob", 1, "b", null, "=b-ob"], 157 | 158 | ["hello", [0, 5], "h", 1, "-hello+h"], 159 | ["yay", [0, 3], "y", 1, "-yay+y"], 160 | ["bobob", [1, 4], "bob", 2, "=b-obo+o=b"], 161 | ].forEach(function (data) { 162 | var oldText = data[0]; 163 | var newText = data[2]; 164 | var oldRange = 165 | typeof data[1] === "number" 166 | ? { index: data[1], length: 0 } 167 | : { index: data[1][0], length: data[1][1] - data[1][0] }; 168 | var newRange = 169 | typeof data[3] === "number" 170 | ? { index: data[3], length: 0 } 171 | : data[3] === null 172 | ? null 173 | : { index: data[3][0], length: data[3][1] - data[3][0] }; 174 | var expected = parseDiff(data[4]); 175 | if (newRange === null && typeof data[1] !== "number") { 176 | throw new Error("invalid test case"); 177 | } 178 | var cursorInfo = 179 | newRange === null 180 | ? data[1] 181 | : { 182 | oldRange: oldRange, 183 | newRange: newRange, 184 | }; 185 | doCursorTest(oldText, newText, cursorInfo, false, expected); 186 | doCursorTest( 187 | "x" + oldText, 188 | "x" + newText, 189 | shiftCursorInfo(cursorInfo, 1), 190 | false, 191 | diffPrepend(expected, "x") 192 | ); 193 | doCursorTest( 194 | oldText + "x", 195 | newText + "x", 196 | cursorInfo, 197 | false, 198 | diffAppend(expected, "x") 199 | ); 200 | 201 | doCursorTest(oldText, newText, cursorInfo, true, expected); 202 | doCursorTest( 203 | "x" + oldText, 204 | "x" + newText, 205 | shiftCursorInfo(cursorInfo, 1), 206 | true, 207 | diffPrepend(expected, "x") 208 | ); 209 | doCursorTest( 210 | oldText + "x", 211 | newText + "x", 212 | cursorInfo, 213 | true, 214 | diffAppend(expected, "x") 215 | ); 216 | }); 217 | 218 | function diffPrepend(tuples, text) { 219 | if (tuples.length > 0 && tuples[0][0] === diff.EQUAL) { 220 | return [[diff.EQUAL, text + tuples[0][1]]].concat(tuples.slice(1)); 221 | } else { 222 | return [[diff.EQUAL, text]].concat(tuples); 223 | } 224 | } 225 | 226 | function diffAppend(tuples, text) { 227 | var lastTuple = tuples[tuples.length - 1]; 228 | if (lastTuple && lastTuple[0] === diff.EQUAL) { 229 | return tuples.slice(0, -1).concat([[diff.EQUAL, lastTuple[1] + text]]); 230 | } else { 231 | return tuples.concat([[diff.EQUAL, text]]); 232 | } 233 | } 234 | 235 | function shiftCursorInfo(cursorInfo, amount) { 236 | if (typeof cursorInfo === "number") { 237 | return cursorInfo + amount; 238 | } else { 239 | return { 240 | oldRange: { 241 | index: cursorInfo.oldRange.index + amount, 242 | length: cursorInfo.oldRange.length, 243 | }, 244 | newRange: { 245 | index: cursorInfo.newRange.index + amount, 246 | length: cursorInfo.newRange.length, 247 | }, 248 | }; 249 | } 250 | } 251 | 252 | function doCursorTest(oldText, newText, cursorInfo, cleanup, expected) { 253 | var result = diff(oldText, newText, cursorInfo, cleanup); 254 | if (!_.isEqual(result, expected)) { 255 | console.log([oldText, newText, cursorInfo]); 256 | console.log(result, "!==", expected); 257 | throw new Error("cursor test failed"); 258 | } 259 | } 260 | 261 | console.log("Running emoji tests"); 262 | [ 263 | ["🐶", "🐯", "-🐶+🐯"], 264 | ["👨🏽", "👩🏽", "-👨+👩=🏽"], 265 | ["👩🏼", "👩🏽", "=👩-🏼+🏽"], 266 | 267 | ["🍏🍎", "🍎", "-🍏=🍎"], 268 | ["🍎", "🍏🍎", "+🍏=🍎"], 269 | ].forEach(function (data) { 270 | var oldText = data[0]; 271 | var newText = data[1]; 272 | var expected = parseDiff(data[2]); 273 | doEmojiTest(oldText, newText, expected); 274 | doEmojiTest("x" + oldText, "x" + newText, diffPrepend(expected, "x")); 275 | doEmojiTest(oldText + "x", newText + "x", diffAppend(expected, "x")); 276 | }); 277 | 278 | function doEmojiTest(oldText, newText, expected) { 279 | var result = diff(oldText, newText); 280 | if (!_.isEqual(result, expected)) { 281 | console.log(oldText, newText, expected); 282 | console.log(result, "!==", expected); 283 | throw new Error("Emoji simple test case failed"); 284 | } 285 | } 286 | 287 | // emojis chosen to share high and low surrogates! 288 | var EMOJI_ALPHABET = ["_", "🤗", "🔗", "🤘", "🔘"]; 289 | 290 | console.log("Generating emoji strings..."); 291 | var emoji_strings = []; 292 | for (var i = 0; i <= ITERATIONS; ++i) { 293 | var letters = []; 294 | var len = Math.floor(random() * EMOJI_MAX_LENGTH); 295 | for (var l = 0; l < len; ++l) { 296 | var letter = EMOJI_ALPHABET[Math.floor(random() * EMOJI_ALPHABET.length)]; 297 | letters.push(letter); 298 | } 299 | emoji_strings.push(letters.join("")); 300 | } 301 | 302 | console.log("Running emoji fuzz tests..."); 303 | for (var i = 0; i < ITERATIONS; ++i) { 304 | var oldText = emoji_strings[i]; 305 | var newText = emoji_strings[i + 1]; 306 | var result = diff(oldText, newText); 307 | applyDiff(result, oldText, newText); 308 | } 309 | 310 | console.log("Running semantic cleanup tests..."); 311 | const semanticCleanupTestCases = [ 312 | // lossless shifting 313 | ["The came", "The cat came"], 314 | ["The cat came", "The came"], 315 | // common overlap 316 | ["111xxxabc", "111defxxx"], 317 | ["111xxxabcd", "111defxxx"], 318 | ]; 319 | semanticCleanupTestCases.forEach(([oldText, newText]) => { 320 | const result = diff(oldText, newText, null, true); 321 | applyDiff(result, oldText, newText); 322 | }); 323 | 324 | // Applies a diff to text, throwing an error if diff is invalid or incorrect 325 | function applyDiff(diffs, text, expectedResult) { 326 | var pos = 0; 327 | function throwError(message) { 328 | console.log(diffs, text, expectedResult); 329 | throw new Error(message); 330 | } 331 | function expect(expected) { 332 | var found = text.substr(pos, expected.length); 333 | if (found !== expected) { 334 | throwError('Expected "' + expected + '", found "' + found + '"'); 335 | } 336 | } 337 | var result = ""; 338 | var inserts_since_last_equality = 0; 339 | var deletes_since_last_equality = 0; 340 | for (var i = 0; i < diffs.length; i++) { 341 | var d = diffs[i]; 342 | if (!d[1]) { 343 | throwError("Empty tuple in diff"); 344 | } 345 | var firstCharCode = d[1].charCodeAt(0); 346 | var lastCharCode = d[1].slice(-1).charCodeAt(0); 347 | if ( 348 | (firstCharCode >= 0xdc00 && firstCharCode <= 0xdfff) || 349 | (lastCharCode >= 0xd800 && lastCharCode <= 0xdbff) 350 | ) { 351 | throwError("Bad unicode diff tuple"); 352 | } 353 | switch (d[0]) { 354 | case diff.EQUAL: 355 | if ( 356 | i !== 0 && 357 | !inserts_since_last_equality && 358 | !deletes_since_last_equality 359 | ) { 360 | throwError("two consecutive equalities in diff"); 361 | } 362 | inserts_since_last_equality = 0; 363 | deletes_since_last_equality = 0; 364 | expect(d[1]); 365 | result += d[1]; 366 | pos += d[1].length; 367 | break; 368 | case diff.DELETE: 369 | if (deletes_since_last_equality) { 370 | throwError("multiple deletes between equalities"); 371 | } 372 | if (inserts_since_last_equality) { 373 | throwError("delete following insert in diff"); 374 | } 375 | deletes_since_last_equality++; 376 | expect(d[1]); 377 | pos += d[1].length; 378 | break; 379 | case diff.INSERT: 380 | if (inserts_since_last_equality) { 381 | throwError("multiple inserts between equalities"); 382 | } 383 | inserts_since_last_equality++; 384 | result += d[1]; 385 | break; 386 | } 387 | } 388 | if (pos !== text.length) { 389 | throwError("Diff did not consume entire input text"); 390 | } 391 | if (result !== expectedResult) { 392 | console.log(diffs, text, expectedResult, result); 393 | throw new Error("Diff not correct"); 394 | } 395 | return result; 396 | } 397 | 398 | console.log("Success!"); 399 | --------------------------------------------------------------------------------