ReactFlow前端拖拽模型与参数配置功能MVP
This commit is contained in:
1 parent
70c91ed019
commit
d3b99304fb
2001 files changed
+349044
-548
No files matched your search
+28
@@ -0,0 +1,28 @@
|
||||
## Building and testing
|
||||
|
||||
```
|
||||
yarn
|
||||
yarn test
|
||||
```
|
||||
|
||||
To run tests in a *browser* (for instance to test compatibility with Firefox, with Safari, or with old browser versions), run `yarn karma start`, then open http://localhost:9876/ in the browser you want to test in. Results of the test run will appear in the terminal where `yarn karma start` is running.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at
|
||||
[http://github.com/kpdecker/jsdiff/issues](http://github.com/kpdecker/jsdiff/issues).
|
||||
|
||||
## Releasing
|
||||
|
||||
Run a test in Firefox via the procedure above before releasing.
|
||||
|
||||
A full release may be completed by first updating the `"version"` property in package.json, then running the following:
|
||||
|
||||
```
|
||||
yarn clean
|
||||
yarn build
|
||||
yarn npm publish
|
||||
```
|
||||
|
||||
After releasing, remember to:
|
||||
* commit the `package.json` change and push it to GitHub
|
||||
* create a new version tag on GitHub
|
||||
* update `diff.js` on the `gh-pages` branch to the latest built version from the `dist/` folder.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2009-2015, Kevin Decker <kpdecker@gmail.com>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+1782
File diff suppressed because it is too large.
Load diff
+1
@@ -0,0 +1 @@
|
||||
((global,factory)=>{"object"==typeof exports&&"undefined"!=typeof module?factory(exports):"function"==typeof define&&define.amd?define(["exports"],factory):factory((global="undefined"!=typeof globalThis?globalThis:global||self).Diff={})})(this,function(exports){class Diff{diff(oldStr,newStr,options={}){let callback;"function"==typeof options?(callback=options,options={}):"callback"in options&&(callback=options.callback);oldStr=this.castInput(oldStr,options),newStr=this.castInput(newStr,options),oldStr=this.removeEmpty(this.tokenize(oldStr,options)),newStr=this.removeEmpty(this.tokenize(newStr,options));return this.diffWithOptionsObj(oldStr,newStr,options,callback)}diffWithOptionsObj(oldTokens,newTokens,options,callback){let _a,done=value=>{if(value=this.postProcess(value,options),!callback)return value;setTimeout(function(){callback(value)},0)},newLen=newTokens.length,oldLen=oldTokens.length,editLength=1,maxEditLength=newLen+oldLen;null!=options.maxEditLength&&(maxEditLength=Math.min(maxEditLength,options.maxEditLength));var maxExecutionTime=null!=(_a=options.timeout)?_a:1/0;let abortAfterTimestamp=Date.now()+maxExecutionTime,bestPath=[{oldPos:-1,lastComponent:void 0}],newPos=this.extractCommon(bestPath[0],newTokens,oldTokens,0,options);if(bestPath[0].oldPos+1>=oldLen&&newPos+1>=newLen)return done(this.buildValues(bestPath[0].lastComponent,newTokens,oldTokens));let minDiagonalToConsider=-1/0,maxDiagonalToConsider=1/0,execEditLength=()=>{for(let diagonalPath=Math.max(minDiagonalToConsider,-editLength);diagonalPath<=Math.min(maxDiagonalToConsider,editLength);diagonalPath+=2){let basePath;var removePath=bestPath[diagonalPath-1],addPath=bestPath[diagonalPath+1];removePath&&(bestPath[diagonalPath-1]=void 0);let canAdd=!1;addPath&&(addPathNewPos=addPath.oldPos-diagonalPath,canAdd=addPath&&0<=addPathNewPos&&addPathNewPos<newLen);var addPathNewPos=removePath&&removePath.oldPos+1<oldLen;if(canAdd||addPathNewPos){if(basePath=!addPathNewPos||canAdd&&removePath.oldPos<addPath.oldPos?this.addToPath(addPath,!0,!1,0,options):this.addToPath(removePath,!1,!0,1,options),newPos=this.extractCommon(basePath,newTokens,oldTokens,diagonalPath,options),basePath.oldPos+1>=oldLen&&newPos+1>=newLen)return done(this.buildValues(basePath.lastComponent,newTokens,oldTokens))||!0;(bestPath[diagonalPath]=basePath).oldPos+1>=oldLen&&(maxDiagonalToConsider=Math.min(maxDiagonalToConsider,diagonalPath-1)),newPos+1>=newLen&&(minDiagonalToConsider=Math.max(minDiagonalToConsider,diagonalPath+1))}else bestPath[diagonalPath]=void 0}editLength++};if(callback)!function exec(){setTimeout(function(){if(editLength>maxEditLength||Date.now()>abortAfterTimestamp)return callback(void 0);execEditLength()||exec()},0)}();else for(;editLength<=maxEditLength&&Date.now()<=abortAfterTimestamp;){var ret=execEditLength();if(ret)return ret}}addToPath(path,added,removed,oldPosInc,options){var last=path.lastComponent;return last&&!options.oneChangePerToken&&last.added===added&&last.removed===removed?{oldPos:path.oldPos+oldPosInc,lastComponent:{count:last.count+1,added:added,removed:removed,previousComponent:last.previousComponent}}:{oldPos:path.oldPos+oldPosInc,lastComponent:{count:1,added:added,removed:removed,previousComponent:last}}}extractCommon(basePath,newTokens,oldTokens,diagonalPath,options){var newLen=newTokens.length,oldLen=oldTokens.length;let oldPos=basePath.oldPos,newPos=oldPos-diagonalPath,commonCount=0;for(;newPos+1<newLen&&oldPos+1<oldLen&&this.equals(oldTokens[oldPos+1],newTokens[newPos+1],options);)newPos++,oldPos++,commonCount++,options.oneChangePerToken&&(basePath.lastComponent={count:1,previousComponent:basePath.lastComponent,added:!1,removed:!1});return commonCount&&!options.oneChangePerToken&&(basePath.lastComponent={count:commonCount,previousComponent:basePath.lastComponent,added:!1,removed:!1}),basePath.oldPos=oldPos,newPos}equals(left,right,options){return options.comparator?options.comparator(left,right):left===right||!!options.ignoreCase&&left.toLowerCase()===right.toLowerCase()}removeEmpty(array){var ret=[];for(let i=0;i<array.length;i++)array[i]&&ret.push(array[i]);return ret}castInput(value,options){return value}tokenize(value,options){return Array.from(value)}join(chars){return chars.join("")}postProcess(changeObjects,options){return changeObjects}get useLongestToken(){return!1}buildValues(lastComponent,newTokens,oldTokens){for(var nextComponent,components=[];lastComponent;)components.push(lastComponent),nextComponent=lastComponent.previousComponent,delete lastComponent.previousComponent,lastComponent=nextComponent;components.reverse();var componentLen=components.length;let componentPos=0,newPos=0,oldPos=0;for(;componentPos<componentLen;componentPos++){var component=components[componentPos];if(component.removed)component.value=this.join(oldTokens.slice(oldPos,oldPos+component.count)),oldPos+=component.count;else{if(!component.added&&this.useLongestToken){let value=newTokens.slice(newPos,newPos+component.count);value=value.map(function(vaLine truncated
|
||||
Generated
Vendored
+182
@@ -0,0 +1,182 @@
|
||||
// @ts-check
|
||||
|
||||
import eslint from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import globals from "globals";
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: [
|
||||
"**/*", // ignore everything...
|
||||
"!src/**/", "!src/**/*.ts", // ... except our TypeScript source files...
|
||||
"!test/**/", "!test/**/*.js", // ... and our tests
|
||||
],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
{
|
||||
files: ['src/**/*.ts'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
extends: [tseslint.configs.recommendedTypeChecked],
|
||||
rules: {
|
||||
// Not sure if these actually serve a purpose, but they provide a way to enforce SOME of what
|
||||
// would be imposed by having "verbatimModuleSyntax": true in our tsconfig.json without
|
||||
// actually doing that.
|
||||
"@typescript-eslint/consistent-type-imports": 2,
|
||||
"@typescript-eslint/consistent-type-exports": 2,
|
||||
|
||||
// Things from the recommendedTypeChecked shared config that are disabled simply because they
|
||||
// caused lots of errors in our existing code when tried. Plausibly useful to turn on if
|
||||
// possible and somebody fancies doing the work:
|
||||
"@typescript-eslint/no-unsafe-argument": 0,
|
||||
"@typescript-eslint/no-unsafe-assignment": 0,
|
||||
"@typescript-eslint/no-unsafe-call": 0,
|
||||
"@typescript-eslint/no-unsafe-member-access": 0,
|
||||
"@typescript-eslint/no-unsafe-return": 0,
|
||||
}
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
// Possible Errors //
|
||||
//-----------------//
|
||||
"comma-dangle": [2, "never"],
|
||||
"no-console": 1, // Allow for debugging
|
||||
"no-debugger": 1, // Allow for debugging
|
||||
"no-extra-parens": [2, "functions"],
|
||||
"no-extra-semi": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-unreachable": 1, // Optimizer and coverage will handle/highlight this and can be useful for debugging
|
||||
|
||||
// Best Practices //
|
||||
//----------------//
|
||||
curly: 2,
|
||||
"default-case": 1,
|
||||
"dot-notation": [2, {
|
||||
allowKeywords: false,
|
||||
}],
|
||||
"guard-for-in": 1,
|
||||
"no-alert": 2,
|
||||
"no-caller": 2,
|
||||
"no-div-regex": 1,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-iterator": 2,
|
||||
"no-labels": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 1,
|
||||
"no-native-reassign": 2,
|
||||
"no-new": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-process-env": 2,
|
||||
"no-proto": 2,
|
||||
"no-return-assign": 2,
|
||||
"no-script-url": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-unused-expressions": 2,
|
||||
"no-warning-comments": 1,
|
||||
radix: 2,
|
||||
"wrap-iife": 2,
|
||||
|
||||
// Variables //
|
||||
//-----------//
|
||||
"no-catch-shadow": 2,
|
||||
"no-label-var": 2,
|
||||
"no-undef-init": 2,
|
||||
|
||||
// Node.js //
|
||||
//---------//
|
||||
|
||||
// Stylistic //
|
||||
//-----------//
|
||||
"brace-style": [2, "1tbs", {
|
||||
allowSingleLine: true,
|
||||
}],
|
||||
camelcase: 2,
|
||||
"comma-spacing": [2, {
|
||||
before: false,
|
||||
after: true,
|
||||
}],
|
||||
"comma-style": [2, "last"],
|
||||
"consistent-this": [1, "self"],
|
||||
"eol-last": 2,
|
||||
"func-style": [2, "declaration"],
|
||||
"key-spacing": [2, {
|
||||
beforeColon: false,
|
||||
afterColon: true,
|
||||
}],
|
||||
"new-cap": 2,
|
||||
"new-parens": 2,
|
||||
"no-array-constructor": 2,
|
||||
"no-lonely-if": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-nested-ternary": 1,
|
||||
"no-new-object": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"quote-props": [2, "as-needed", {
|
||||
keywords: true,
|
||||
}],
|
||||
quotes: [2, "single", "avoid-escape"],
|
||||
semi: 2,
|
||||
"semi-spacing": [2, {
|
||||
before: false,
|
||||
after: true,
|
||||
}],
|
||||
"space-before-blocks": [2, "always"],
|
||||
"space-before-function-paren": [2, {
|
||||
anonymous: "never",
|
||||
named: "never",
|
||||
}],
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-infix-ops": 2,
|
||||
"space-unary-ops": 2,
|
||||
"spaced-comment": [2, "always"],
|
||||
"wrap-regex": 1,
|
||||
"no-var": 2,
|
||||
|
||||
// Typescript //
|
||||
//------------//
|
||||
"@typescript-eslint/no-explicit-any": 0, // Very strict rule, incompatible with our code
|
||||
|
||||
// We use these intentionally - e.g.
|
||||
// export interface DiffCssOptions extends CommonDiffOptions {}
|
||||
// for the options argument to diffCss which currently takes no options beyond the ones
|
||||
// common to all diffFoo functions. Doing this allows consistency (one options interface per
|
||||
// diffFoo function) and future-proofs against the API having to change in future if we add a
|
||||
// non-common option to one of these functions.
|
||||
"@typescript-eslint/no-empty-object-type": [2, {allowInterfaces: 'with-single-extends'}],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['test/**/*.js'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.mocha,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-unused-expressions": 0, // Needs disabling to support Chai `.to.be.undefined` etc syntax
|
||||
"@typescript-eslint/no-unused-expressions": 0, // (as above)
|
||||
},
|
||||
}
|
||||
);
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.convertChangesToDMP = convertChangesToDMP;
|
||||
/**
|
||||
* converts a list of change objects to the format returned by Google's [diff-match-patch](https://github.com/google/diff-match-patch) library
|
||||
*/
|
||||
function convertChangesToDMP(changes) {
|
||||
var ret = [];
|
||||
var change, operation;
|
||||
for (var i = 0; i < changes.length; i++) {
|
||||
change = changes[i];
|
||||
if (change.added) {
|
||||
operation = 1;
|
||||
}
|
||||
else if (change.removed) {
|
||||
operation = -1;
|
||||
}
|
||||
else {
|
||||
operation = 0;
|
||||
}
|
||||
ret.push([operation, change.value]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.convertChangesToXML = convertChangesToXML;
|
||||
/**
|
||||
* converts a list of change objects to a serialized XML format
|
||||
*/
|
||||
function convertChangesToXML(changes) {
|
||||
var ret = [];
|
||||
for (var i = 0; i < changes.length; i++) {
|
||||
var change = changes[i];
|
||||
if (change.added) {
|
||||
ret.push('<ins>');
|
||||
}
|
||||
else if (change.removed) {
|
||||
ret.push('<del>');
|
||||
}
|
||||
ret.push(escapeHTML(change.value));
|
||||
if (change.added) {
|
||||
ret.push('</ins>');
|
||||
}
|
||||
else if (change.removed) {
|
||||
ret.push('</del>');
|
||||
}
|
||||
}
|
||||
return ret.join('');
|
||||
}
|
||||
function escapeHTML(s) {
|
||||
var n = s;
|
||||
n = n.replace(/&/g, '&');
|
||||
n = n.replace(/</g, '<');
|
||||
n = n.replace(/>/g, '>');
|
||||
n = n.replace(/"/g, '"');
|
||||
return n;
|
||||
}
|
||||
Generated
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
"use strict";
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.arrayDiff = void 0;
|
||||
exports.diffArrays = diffArrays;
|
||||
var base_js_1 = require("./base.js");
|
||||
var ArrayDiff = /** @class */ (function (_super) {
|
||||
__extends(ArrayDiff, _super);
|
||||
function ArrayDiff() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
ArrayDiff.prototype.tokenize = function (value) {
|
||||
return value.slice();
|
||||
};
|
||||
ArrayDiff.prototype.join = function (value) {
|
||||
return value;
|
||||
};
|
||||
ArrayDiff.prototype.removeEmpty = function (value) {
|
||||
return value;
|
||||
};
|
||||
return ArrayDiff;
|
||||
}(base_js_1.default));
|
||||
exports.arrayDiff = new ArrayDiff();
|
||||
function diffArrays(oldArr, newArr, options) {
|
||||
return exports.arrayDiff.diff(oldArr, newArr, options);
|
||||
}
|
||||
Generated
Vendored
+265
@@ -0,0 +1,265 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var Diff = /** @class */ (function () {
|
||||
function Diff() {
|
||||
}
|
||||
Diff.prototype.diff = function (oldStr, newStr,
|
||||
// Type below is not accurate/complete - see above for full possibilities - but it compiles
|
||||
options) {
|
||||
if (options === void 0) { options = {}; }
|
||||
var callback;
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
else if ('callback' in options) {
|
||||
callback = options.callback;
|
||||
}
|
||||
// Allow subclasses to massage the input prior to running
|
||||
var oldString = this.castInput(oldStr, options);
|
||||
var newString = this.castInput(newStr, options);
|
||||
var oldTokens = this.removeEmpty(this.tokenize(oldString, options));
|
||||
var newTokens = this.removeEmpty(this.tokenize(newString, options));
|
||||
return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
|
||||
};
|
||||
Diff.prototype.diffWithOptionsObj = function (oldTokens, newTokens, options, callback) {
|
||||
var _this = this;
|
||||
var _a;
|
||||
var done = function (value) {
|
||||
value = _this.postProcess(value, options);
|
||||
if (callback) {
|
||||
setTimeout(function () { callback(value); }, 0);
|
||||
return undefined;
|
||||
}
|
||||
else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
var newLen = newTokens.length, oldLen = oldTokens.length;
|
||||
var editLength = 1;
|
||||
var maxEditLength = newLen + oldLen;
|
||||
if (options.maxEditLength != null) {
|
||||
maxEditLength = Math.min(maxEditLength, options.maxEditLength);
|
||||
}
|
||||
var maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
|
||||
var abortAfterTimestamp = Date.now() + maxExecutionTime;
|
||||
var bestPath = [{ oldPos: -1, lastComponent: undefined }];
|
||||
// Seed editLength = 0, i.e. the content starts with the same values
|
||||
var newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
|
||||
if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
|
||||
// Identity per the equality and tokenizer
|
||||
return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
|
||||
}
|
||||
// Once we hit the right edge of the edit graph on some diagonal k, we can
|
||||
// definitely reach the end of the edit graph in no more than k edits, so
|
||||
// there's no point in considering any moves to diagonal k+1 any more (from
|
||||
// which we're guaranteed to need at least k+1 more edits).
|
||||
// Similarly, once we've reached the bottom of the edit graph, there's no
|
||||
// point considering moves to lower diagonals.
|
||||
// We record this fact by setting minDiagonalToConsider and
|
||||
// maxDiagonalToConsider to some finite value once we've hit the edge of
|
||||
// the edit graph.
|
||||
// This optimization is not faithful to the original algorithm presented in
|
||||
// Myers's paper, which instead pointlessly extends D-paths off the end of
|
||||
// the edit graph - see page 7 of Myers's paper which notes this point
|
||||
// explicitly and illustrates it with a diagram. This has major performance
|
||||
// implications for some common scenarios. For instance, to compute a diff
|
||||
// where the new text simply appends d characters on the end of the
|
||||
// original text of length n, the true Myers algorithm will take O(n+d^2)
|
||||
// time while this optimization needs only O(n+d) time.
|
||||
var minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
|
||||
// Main worker method. checks all permutations of a given edit length for acceptance.
|
||||
var execEditLength = function () {
|
||||
for (var diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
|
||||
var basePath = void 0;
|
||||
var removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
|
||||
if (removePath) {
|
||||
// No one else is going to attempt to use this value, clear it
|
||||
// @ts-expect-error - perf optimisation. This type-violating value will never be read.
|
||||
bestPath[diagonalPath - 1] = undefined;
|
||||
}
|
||||
var canAdd = false;
|
||||
if (addPath) {
|
||||
// what newPos will be after we do an insertion:
|
||||
var addPathNewPos = addPath.oldPos - diagonalPath;
|
||||
canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
|
||||
}
|
||||
var canRemove = removePath && removePath.oldPos + 1 < oldLen;
|
||||
if (!canAdd && !canRemove) {
|
||||
// If this path is a terminal then prune
|
||||
// @ts-expect-error - perf optimisation. This type-violating value will never be read.
|
||||
bestPath[diagonalPath] = undefined;
|
||||
continue;
|
||||
}
|
||||
// Select the diagonal that we want to branch from. We select the prior
|
||||
// path whose position in the old string is the farthest from the origin
|
||||
// and does not pass the bounds of the diff graph
|
||||
if (!canRemove || (canAdd && removePath.oldPos < addPath.oldPos)) {
|
||||
basePath = _this.addToPath(addPath, true, false, 0, options);
|
||||
}
|
||||
else {
|
||||
basePath = _this.addToPath(removePath, false, true, 1, options);
|
||||
}
|
||||
newPos = _this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
|
||||
if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
|
||||
// If we have hit the end of both strings, then we are done
|
||||
return done(_this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
|
||||
}
|
||||
else {
|
||||
bestPath[diagonalPath] = basePath;
|
||||
if (basePath.oldPos + 1 >= oldLen) {
|
||||
maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
|
||||
}
|
||||
if (newPos + 1 >= newLen) {
|
||||
minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
editLength++;
|
||||
};
|
||||
// Performs the length of edit iteration. Is a bit fugly as this has to support the
|
||||
// sync and async mode which is never fun. Loops over execEditLength until a value
|
||||
// is produced, or until the edit length exceeds options.maxEditLength (if given),
|
||||
// in which case it will return undefined.
|
||||
if (callback) {
|
||||
(function exec() {
|
||||
setTimeout(function () {
|
||||
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
|
||||
return callback(undefined);
|
||||
}
|
||||
if (!execEditLength()) {
|
||||
exec();
|
||||
}
|
||||
}, 0);
|
||||
}());
|
||||
}
|
||||
else {
|
||||
while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
|
||||
var ret = execEditLength();
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Diff.prototype.addToPath = function (path, added, removed, oldPosInc, options) {
|
||||
var last = path.lastComponent;
|
||||
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
||||
return {
|
||||
oldPos: path.oldPos + oldPosInc,
|
||||
lastComponent: { count: last.count + 1, added: added, removed: removed, previousComponent: last.previousComponent }
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
oldPos: path.oldPos + oldPosInc,
|
||||
lastComponent: { count: 1, added: added, removed: removed, previousComponent: last }
|
||||
};
|
||||
}
|
||||
};
|
||||
Diff.prototype.extractCommon = function (basePath, newTokens, oldTokens, diagonalPath, options) {
|
||||
var newLen = newTokens.length, oldLen = oldTokens.length;
|
||||
var oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
|
||||
while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
|
||||
newPos++;
|
||||
oldPos++;
|
||||
commonCount++;
|
||||
if (options.oneChangePerToken) {
|
||||
basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
|
||||
}
|
||||
}
|
||||
if (commonCount && !options.oneChangePerToken) {
|
||||
basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
|
||||
}
|
||||
basePath.oldPos = oldPos;
|
||||
return newPos;
|
||||
};
|
||||
Diff.prototype.equals = function (left, right, options) {
|
||||
if (options.comparator) {
|
||||
return options.comparator(left, right);
|
||||
}
|
||||
else {
|
||||
return left === right
|
||||
|| (!!options.ignoreCase && left.toLowerCase() === right.toLowerCase());
|
||||
}
|
||||
};
|
||||
Diff.prototype.removeEmpty = function (array) {
|
||||
var ret = [];
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
if (array[i]) {
|
||||
ret.push(array[i]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
Diff.prototype.castInput = function (value, options) {
|
||||
return value;
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
Diff.prototype.tokenize = function (value, options) {
|
||||
return Array.from(value);
|
||||
};
|
||||
Diff.prototype.join = function (chars) {
|
||||
// Assumes ValueT is string, which is the case for most subclasses.
|
||||
// When it's false, e.g. in diffArrays, this method needs to be overridden (e.g. with a no-op)
|
||||
// Yes, the casts are verbose and ugly, because this pattern - of having the base class SORT OF
|
||||
// assume tokens and values are strings, but not completely - is weird and janky.
|
||||
return chars.join('');
|
||||
};
|
||||
Diff.prototype.postProcess = function (changeObjects,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
options) {
|
||||
return changeObjects;
|
||||
};
|
||||
Object.defineProperty(Diff.prototype, "useLongestToken", {
|
||||
get: function () {
|
||||
return false;
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Diff.prototype.buildValues = function (lastComponent, newTokens, oldTokens) {
|
||||
// First we convert our linked list of components in reverse order to an
|
||||
// array in the right order:
|
||||
var components = [];
|
||||
var nextComponent;
|
||||
while (lastComponent) {
|
||||
components.push(lastComponent);
|
||||
nextComponent = lastComponent.previousComponent;
|
||||
delete lastComponent.previousComponent;
|
||||
lastComponent = nextComponent;
|
||||
}
|
||||
components.reverse();
|
||||
var componentLen = components.length;
|
||||
var componentPos = 0, newPos = 0, oldPos = 0;
|
||||
for (; componentPos < componentLen; componentPos++) {
|
||||
var component = components[componentPos];
|
||||
if (!component.removed) {
|
||||
if (!component.added && this.useLongestToken) {
|
||||
var value = newTokens.slice(newPos, newPos + component.count);
|
||||
value = value.map(function (value, i) {
|
||||
var oldValue = oldTokens[oldPos + i];
|
||||
return oldValue.length > value.length ? oldValue : value;
|
||||
});
|
||||
component.value = this.join(value);
|
||||
}
|
||||
else {
|
||||
component.value = this.join(newTokens.slice(newPos, newPos + component.count));
|
||||
}
|
||||
newPos += component.count;
|
||||
// Common case
|
||||
if (!component.added) {
|
||||
oldPos += component.count;
|
||||
}
|
||||
}
|
||||
else {
|
||||
component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
|
||||
oldPos += component.count;
|
||||
}
|
||||
}
|
||||
return components;
|
||||
};
|
||||
return Diff;
|
||||
}());
|
||||
exports.default = Diff;
|
||||
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.characterDiff = void 0;
|
||||
exports.diffChars = diffChars;
|
||||
var base_js_1 = require("./base.js");
|
||||
var CharacterDiff = /** @class */ (function (_super) {
|
||||
__extends(CharacterDiff, _super);
|
||||
function CharacterDiff() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
return CharacterDiff;
|
||||
}(base_js_1.default));
|
||||
exports.characterDiff = new CharacterDiff();
|
||||
function diffChars(oldStr, newStr, options) {
|
||||
return exports.characterDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
"use strict";
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.cssDiff = void 0;
|
||||
exports.diffCss = diffCss;
|
||||
var base_js_1 = require("./base.js");
|
||||
var CssDiff = /** @class */ (function (_super) {
|
||||
__extends(CssDiff, _super);
|
||||
function CssDiff() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
CssDiff.prototype.tokenize = function (value) {
|
||||
return value.split(/([{}:;,]|\s+)/);
|
||||
};
|
||||
return CssDiff;
|
||||
}(base_js_1.default));
|
||||
exports.cssDiff = new CssDiff();
|
||||
function diffCss(oldStr, newStr, options) {
|
||||
return exports.cssDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
Generated
Vendored
+105
@@ -0,0 +1,105 @@
|
||||
"use strict";
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.jsonDiff = void 0;
|
||||
exports.diffJson = diffJson;
|
||||
exports.canonicalize = canonicalize;
|
||||
var base_js_1 = require("./base.js");
|
||||
var line_js_1 = require("./line.js");
|
||||
var JsonDiff = /** @class */ (function (_super) {
|
||||
__extends(JsonDiff, _super);
|
||||
function JsonDiff() {
|
||||
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||
_this.tokenize = line_js_1.tokenize;
|
||||
return _this;
|
||||
}
|
||||
Object.defineProperty(JsonDiff.prototype, "useLongestToken", {
|
||||
get: function () {
|
||||
// Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
|
||||
// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
|
||||
return true;
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
JsonDiff.prototype.castInput = function (value, options) {
|
||||
var undefinedReplacement = options.undefinedReplacement, _a = options.stringifyReplacer, stringifyReplacer = _a === void 0 ? function (k, v) { return typeof v === 'undefined' ? undefinedReplacement : v; } : _a;
|
||||
return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), null, ' ');
|
||||
};
|
||||
JsonDiff.prototype.equals = function (left, right, options) {
|
||||
return _super.prototype.equals.call(this, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options);
|
||||
};
|
||||
return JsonDiff;
|
||||
}(base_js_1.default));
|
||||
exports.jsonDiff = new JsonDiff();
|
||||
function diffJson(oldStr, newStr, options) {
|
||||
return exports.jsonDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
// This function handles the presence of circular references by bailing out when encountering an
|
||||
// object that is already on the "stack" of items being processed. Accepts an optional replacer
|
||||
function canonicalize(obj, stack, replacementStack, replacer, key) {
|
||||
stack = stack || [];
|
||||
replacementStack = replacementStack || [];
|
||||
if (replacer) {
|
||||
obj = replacer(key === undefined ? '' : key, obj);
|
||||
}
|
||||
var i;
|
||||
for (i = 0; i < stack.length; i += 1) {
|
||||
if (stack[i] === obj) {
|
||||
return replacementStack[i];
|
||||
}
|
||||
}
|
||||
var canonicalizedObj;
|
||||
if ('[object Array]' === Object.prototype.toString.call(obj)) {
|
||||
stack.push(obj);
|
||||
canonicalizedObj = new Array(obj.length);
|
||||
replacementStack.push(canonicalizedObj);
|
||||
for (i = 0; i < obj.length; i += 1) {
|
||||
canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, String(i));
|
||||
}
|
||||
stack.pop();
|
||||
replacementStack.pop();
|
||||
return canonicalizedObj;
|
||||
}
|
||||
if (obj && obj.toJSON) {
|
||||
obj = obj.toJSON();
|
||||
}
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
stack.push(obj);
|
||||
canonicalizedObj = {};
|
||||
replacementStack.push(canonicalizedObj);
|
||||
var sortedKeys = [];
|
||||
var key_1;
|
||||
for (key_1 in obj) {
|
||||
/* istanbul ignore else */
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key_1)) {
|
||||
sortedKeys.push(key_1);
|
||||
}
|
||||
}
|
||||
sortedKeys.sort();
|
||||
for (i = 0; i < sortedKeys.length; i += 1) {
|
||||
key_1 = sortedKeys[i];
|
||||
canonicalizedObj[key_1] = canonicalize(obj[key_1], stack, replacementStack, replacer, key_1);
|
||||
}
|
||||
stack.pop();
|
||||
replacementStack.pop();
|
||||
}
|
||||
else {
|
||||
canonicalizedObj = obj;
|
||||
}
|
||||
return canonicalizedObj;
|
||||
}
|
||||
Generated
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
"use strict";
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.lineDiff = void 0;
|
||||
exports.diffLines = diffLines;
|
||||
exports.diffTrimmedLines = diffTrimmedLines;
|
||||
exports.tokenize = tokenize;
|
||||
var base_js_1 = require("./base.js");
|
||||
var params_js_1 = require("../util/params.js");
|
||||
var LineDiff = /** @class */ (function (_super) {
|
||||
__extends(LineDiff, _super);
|
||||
function LineDiff() {
|
||||
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||
_this.tokenize = tokenize;
|
||||
return _this;
|
||||
}
|
||||
LineDiff.prototype.equals = function (left, right, options) {
|
||||
// If we're ignoring whitespace, we need to normalise lines by stripping
|
||||
// whitespace before checking equality. (This has an annoying interaction
|
||||
// with newlineIsToken that requires special handling: if newlines get their
|
||||
// own token, then we DON'T want to trim the *newline* tokens down to empty
|
||||
// strings, since this would cause us to treat whitespace-only line content
|
||||
// as equal to a separator between lines, which would be weird and
|
||||
// inconsistent with the documented behavior of the options.)
|
||||
if (options.ignoreWhitespace) {
|
||||
if (!options.newlineIsToken || !left.includes('\n')) {
|
||||
left = left.trim();
|
||||
}
|
||||
if (!options.newlineIsToken || !right.includes('\n')) {
|
||||
right = right.trim();
|
||||
}
|
||||
}
|
||||
else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
|
||||
if (left.endsWith('\n')) {
|
||||
left = left.slice(0, -1);
|
||||
}
|
||||
if (right.endsWith('\n')) {
|
||||
right = right.slice(0, -1);
|
||||
}
|
||||
}
|
||||
return _super.prototype.equals.call(this, left, right, options);
|
||||
};
|
||||
return LineDiff;
|
||||
}(base_js_1.default));
|
||||
exports.lineDiff = new LineDiff();
|
||||
function diffLines(oldStr, newStr, options) {
|
||||
return exports.lineDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
function diffTrimmedLines(oldStr, newStr, options) {
|
||||
options = (0, params_js_1.generateOptions)(options, { ignoreWhitespace: true });
|
||||
return exports.lineDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
// Exported standalone so it can be used from jsonDiff too.
|
||||
function tokenize(value, options) {
|
||||
if (options.stripTrailingCr) {
|
||||
// remove one \r before \n to match GNU diff's --strip-trailing-cr behavior
|
||||
value = value.replace(/\r\n/g, '\n');
|
||||
}
|
||||
var retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
|
||||
// Ignore the final empty token that occurs if the string ends with a new line
|
||||
if (!linesAndNewlines[linesAndNewlines.length - 1]) {
|
||||
linesAndNewlines.pop();
|
||||
}
|
||||
// Merge the content and line separators into single tokens
|
||||
for (var i = 0; i < linesAndNewlines.length; i++) {
|
||||
var line = linesAndNewlines[i];
|
||||
if (i % 2 && !options.newlineIsToken) {
|
||||
retLines[retLines.length - 1] += line;
|
||||
}
|
||||
else {
|
||||
retLines.push(line);
|
||||
}
|
||||
}
|
||||
return retLines;
|
||||
}
|
||||
Generated
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
"use strict";
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sentenceDiff = void 0;
|
||||
exports.diffSentences = diffSentences;
|
||||
var base_js_1 = require("./base.js");
|
||||
function isSentenceEndPunct(char) {
|
||||
return char == '.' || char == '!' || char == '?';
|
||||
}
|
||||
var SentenceDiff = /** @class */ (function (_super) {
|
||||
__extends(SentenceDiff, _super);
|
||||
function SentenceDiff() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
SentenceDiff.prototype.tokenize = function (value) {
|
||||
var _a;
|
||||
// If in future we drop support for environments that don't support lookbehinds, we can replace
|
||||
// this entire function with:
|
||||
// return value.split(/(?<=[.!?])(\s+|$)/);
|
||||
// but until then, for similar reasons to the trailingWs function in string.ts, we are forced
|
||||
// to do this verbosely "by hand" instead of using a regex.
|
||||
var result = [];
|
||||
var tokenStartI = 0;
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
if (i == value.length - 1) {
|
||||
result.push(value.slice(tokenStartI));
|
||||
break;
|
||||
}
|
||||
if (isSentenceEndPunct(value[i]) && value[i + 1].match(/\s/)) {
|
||||
// We've hit a sentence break - i.e. a punctuation mark followed by whitespace.
|
||||
// We now want to push TWO tokens to the result:
|
||||
// 1. the sentence
|
||||
result.push(value.slice(tokenStartI, i + 1));
|
||||
// 2. the whitespace
|
||||
i = tokenStartI = i + 1;
|
||||
while ((_a = value[i + 1]) === null || _a === void 0 ? void 0 : _a.match(/\s/)) {
|
||||
i++;
|
||||
}
|
||||
result.push(value.slice(tokenStartI, i + 1));
|
||||
// Then the next token (a sentence) starts on the character after the whitespace.
|
||||
// (It's okay if this is off the end of the string - then the outer loop will terminate
|
||||
// here anyway.)
|
||||
tokenStartI = i + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
return SentenceDiff;
|
||||
}(base_js_1.default));
|
||||
exports.sentenceDiff = new SentenceDiff();
|
||||
function diffSentences(oldStr, newStr, options) {
|
||||
return exports.sentenceDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
Generated
Vendored
+312
@@ -0,0 +1,312 @@
|
||||
"use strict";
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.wordsWithSpaceDiff = exports.wordDiff = void 0;
|
||||
exports.diffWords = diffWords;
|
||||
exports.diffWordsWithSpace = diffWordsWithSpace;
|
||||
var base_js_1 = require("./base.js");
|
||||
var string_js_1 = require("../util/string.js");
|
||||
// Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode
|
||||
//
|
||||
// Chars/ranges counted as "word" characters by this regex are as follows:
|
||||
//
|
||||
// + U+00AD Soft hyphen
|
||||
// + 00C0–00FF (letters with diacritics from the Latin-1 Supplement), except:
|
||||
// - U+00D7 × Multiplication sign
|
||||
// - U+00F7 ÷ Division sign
|
||||
// + Latin Extended-A, 0100–017F
|
||||
// + Latin Extended-B, 0180–024F
|
||||
// + IPA Extensions, 0250–02AF
|
||||
// + Spacing Modifier Letters, 02B0–02FF, except:
|
||||
// - U+02C7 ˇ ˇ Caron
|
||||
// - U+02D8 ˘ ˘ Breve
|
||||
// - U+02D9 ˙ ˙ Dot Above
|
||||
// - U+02DA ˚ ˚ Ring Above
|
||||
// - U+02DB ˛ ˛ Ogonek
|
||||
// - U+02DC ˜ ˜ Small Tilde
|
||||
// - U+02DD ˝ ˝ Double Acute Accent
|
||||
// + Latin Extended Additional, 1E00–1EFF
|
||||
var extendedWordChars = 'a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}';
|
||||
// Each token is one of the following:
|
||||
// - A punctuation mark plus the surrounding whitespace
|
||||
// - A word plus the surrounding whitespace
|
||||
// - Pure whitespace (but only in the special case where the entire text
|
||||
// is just whitespace)
|
||||
//
|
||||
// We have to include surrounding whitespace in the tokens because the two
|
||||
// alternative approaches produce horribly broken results:
|
||||
// * If we just discard the whitespace, we can't fully reproduce the original
|
||||
// text from the sequence of tokens and any attempt to render the diff will
|
||||
// get the whitespace wrong.
|
||||
// * If we have separate tokens for whitespace, then in a typical text every
|
||||
// second token will be a single space character. But this often results in
|
||||
// the optimal diff between two texts being a perverse one that preserves
|
||||
// the spaces between words but deletes and reinserts actual common words.
|
||||
// See https://github.com/kpdecker/jsdiff/issues/160#issuecomment-1866099640
|
||||
// for an example.
|
||||
//
|
||||
// Keeping the surrounding whitespace of course has implications for .equals
|
||||
// and .join, not just .tokenize.
|
||||
// This regex does NOT fully implement the tokenization rules described above.
|
||||
// Instead, it gives runs of whitespace their own "token". The tokenize method
|
||||
// then handles stitching whitespace tokens onto adjacent word or punctuation
|
||||
// tokens.
|
||||
var tokenizeIncludingWhitespace = new RegExp("[".concat(extendedWordChars, "]+|\\s+|[^").concat(extendedWordChars, "]"), 'ug');
|
||||
var WordDiff = /** @class */ (function (_super) {
|
||||
__extends(WordDiff, _super);
|
||||
function WordDiff() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
WordDiff.prototype.equals = function (left, right, options) {
|
||||
if (options.ignoreCase) {
|
||||
left = left.toLowerCase();
|
||||
right = right.toLowerCase();
|
||||
}
|
||||
return left.trim() === right.trim();
|
||||
};
|
||||
WordDiff.prototype.tokenize = function (value, options) {
|
||||
if (options === void 0) { options = {}; }
|
||||
var parts;
|
||||
if (options.intlSegmenter) {
|
||||
var segmenter = options.intlSegmenter;
|
||||
if (segmenter.resolvedOptions().granularity != 'word') {
|
||||
throw new Error('The segmenter passed must have a granularity of "word"');
|
||||
}
|
||||
// We want `parts` to be an array whose elements alternate between being
|
||||
// pure whitespace and being pure non-whitespace. This is ALMOST what the
|
||||
// segments returned by a word-based Intl.Segmenter already look like,
|
||||
// but not quite - see explanation in the docs of our custom segment()
|
||||
// function.
|
||||
parts = (0, string_js_1.segment)(value, segmenter);
|
||||
}
|
||||
else {
|
||||
parts = value.match(tokenizeIncludingWhitespace) || [];
|
||||
}
|
||||
var tokens = [];
|
||||
var prevPart = null;
|
||||
parts.forEach(function (part) {
|
||||
if ((/\s/).test(part)) {
|
||||
if (prevPart == null) {
|
||||
tokens.push(part);
|
||||
}
|
||||
else {
|
||||
tokens.push(tokens.pop() + part);
|
||||
}
|
||||
}
|
||||
else if (prevPart != null && (/\s/).test(prevPart)) {
|
||||
if (tokens[tokens.length - 1] == prevPart) {
|
||||
tokens.push(tokens.pop() + part);
|
||||
}
|
||||
else {
|
||||
tokens.push(prevPart + part);
|
||||
}
|
||||
}
|
||||
else {
|
||||
tokens.push(part);
|
||||
}
|
||||
prevPart = part;
|
||||
});
|
||||
return tokens;
|
||||
};
|
||||
WordDiff.prototype.join = function (tokens) {
|
||||
// Tokens being joined here will always have appeared consecutively in the
|
||||
// same text, so we can simply strip off the leading whitespace from all the
|
||||
// tokens except the first (and except any whitespace-only tokens - but such
|
||||
// a token will always be the first and only token anyway) and then join them
|
||||
// and the whitespace around words and punctuation will end up correct.
|
||||
return tokens.map(function (token, i) {
|
||||
if (i == 0) {
|
||||
return token;
|
||||
}
|
||||
else {
|
||||
return token.replace((/^\s+/), '');
|
||||
}
|
||||
}).join('');
|
||||
};
|
||||
WordDiff.prototype.postProcess = function (changes, options) {
|
||||
if (!changes || options.oneChangePerToken) {
|
||||
return changes;
|
||||
}
|
||||
var lastKeep = null;
|
||||
// Change objects representing any insertion or deletion since the last
|
||||
// "keep" change object. There can be at most one of each.
|
||||
var insertion = null;
|
||||
var deletion = null;
|
||||
changes.forEach(function (change) {
|
||||
if (change.added) {
|
||||
insertion = change;
|
||||
}
|
||||
else if (change.removed) {
|
||||
deletion = change;
|
||||
}
|
||||
else {
|
||||
if (insertion || deletion) { // May be false at start of text
|
||||
dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change, options.intlSegmenter);
|
||||
}
|
||||
lastKeep = change;
|
||||
insertion = null;
|
||||
deletion = null;
|
||||
}
|
||||
});
|
||||
if (insertion || deletion) {
|
||||
dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null, options.intlSegmenter);
|
||||
}
|
||||
return changes;
|
||||
};
|
||||
return WordDiff;
|
||||
}(base_js_1.default));
|
||||
exports.wordDiff = new WordDiff();
|
||||
function diffWords(oldStr, newStr, options) {
|
||||
// This option has never been documented and never will be (it's clearer to
|
||||
// just call `diffWordsWithSpace` directly if you need that behavior), but
|
||||
// has existed in jsdiff for a long time, so we retain support for it here
|
||||
// for the sake of backwards compatibility.
|
||||
if ((options === null || options === void 0 ? void 0 : options.ignoreWhitespace) != null && !options.ignoreWhitespace) {
|
||||
return diffWordsWithSpace(oldStr, newStr, options);
|
||||
}
|
||||
return exports.wordDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep, segmenter) {
|
||||
// Before returning, we tidy up the leading and trailing whitespace of the
|
||||
// change objects to eliminate cases where trailing whitespace in one object
|
||||
// is repeated as leading whitespace in the next.
|
||||
// Below are examples of the outcomes we want here to explain the code.
|
||||
// I=insert, K=keep, D=delete
|
||||
// 1. diffing 'foo bar baz' vs 'foo baz'
|
||||
// Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
|
||||
// After cleanup, we want: K:'foo ' D:'bar ' K:'baz'
|
||||
//
|
||||
// 2. Diffing 'foo bar baz' vs 'foo qux baz'
|
||||
// Prior to cleanup, we have K:'foo ' D:' bar ' I:' qux ' K:' baz'
|
||||
// After cleanup, we want K:'foo ' D:'bar' I:'qux' K:' baz'
|
||||
//
|
||||
// 3. Diffing 'foo\nbar baz' vs 'foo baz'
|
||||
// Prior to cleanup, we have K:'foo ' D:'\nbar ' K:' baz'
|
||||
// After cleanup, we want K'foo' D:'\nbar' K:' baz'
|
||||
//
|
||||
// 4. Diffing 'foo baz' vs 'foo\nbar baz'
|
||||
// Prior to cleanup, we have K:'foo\n' I:'\nbar ' K:' baz'
|
||||
// After cleanup, we ideally want K'foo' I:'\nbar' K:' baz'
|
||||
// but don't actually manage this currently (the pre-cleanup change
|
||||
// objects don't contain enough information to make it possible).
|
||||
//
|
||||
// 5. Diffing 'foo bar baz' vs 'foo baz'
|
||||
// Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
|
||||
// After cleanup, we want K:'foo ' D:' bar ' K:'baz'
|
||||
//
|
||||
// Our handling is unavoidably imperfect in the case where there's a single
|
||||
// indel between keeps and the whitespace has changed. For instance, consider
|
||||
// diffing 'foo\tbar\nbaz' vs 'foo baz'. Unless we create an extra change
|
||||
// object to represent the insertion of the space character (which isn't even
|
||||
// a token), we have no way to avoid losing information about the texts'
|
||||
// original whitespace in the result we return. Still, we do our best to
|
||||
// output something that will look sensible if we e.g. print it with
|
||||
// insertions in green and deletions in red.
|
||||
// Between two "keep" change objects (or before the first or after the last
|
||||
// change object), we can have either:
|
||||
// * A "delete" followed by an "insert"
|
||||
// * Just an "insert"
|
||||
// * Just a "delete"
|
||||
// We handle the three cases separately.
|
||||
if (deletion && insertion) {
|
||||
var _a = (0, string_js_1.leadingAndTrailingWs)(deletion.value, segmenter), oldWsPrefix = _a[0], oldWsSuffix = _a[1];
|
||||
var _b = (0, string_js_1.leadingAndTrailingWs)(insertion.value, segmenter), newWsPrefix = _b[0], newWsSuffix = _b[1];
|
||||
if (startKeep) {
|
||||
var commonWsPrefix = (0, string_js_1.longestCommonPrefix)(oldWsPrefix, newWsPrefix);
|
||||
startKeep.value = (0, string_js_1.replaceSuffix)(startKeep.value, newWsPrefix, commonWsPrefix);
|
||||
deletion.value = (0, string_js_1.removePrefix)(deletion.value, commonWsPrefix);
|
||||
insertion.value = (0, string_js_1.removePrefix)(insertion.value, commonWsPrefix);
|
||||
}
|
||||
if (endKeep) {
|
||||
var commonWsSuffix = (0, string_js_1.longestCommonSuffix)(oldWsSuffix, newWsSuffix);
|
||||
endKeep.value = (0, string_js_1.replacePrefix)(endKeep.value, newWsSuffix, commonWsSuffix);
|
||||
deletion.value = (0, string_js_1.removeSuffix)(deletion.value, commonWsSuffix);
|
||||
insertion.value = (0, string_js_1.removeSuffix)(insertion.value, commonWsSuffix);
|
||||
}
|
||||
}
|
||||
else if (insertion) {
|
||||
// The whitespaces all reflect what was in the new text rather than
|
||||
// the old, so we essentially have no information about whitespace
|
||||
// insertion or deletion. We just want to dedupe the whitespace.
|
||||
// We do that by having each change object keep its trailing
|
||||
// whitespace and deleting duplicate leading whitespace where
|
||||
// present.
|
||||
if (startKeep) {
|
||||
var ws = (0, string_js_1.leadingWs)(insertion.value, segmenter);
|
||||
insertion.value = insertion.value.substring(ws.length);
|
||||
}
|
||||
if (endKeep) {
|
||||
var ws = (0, string_js_1.leadingWs)(endKeep.value, segmenter);
|
||||
endKeep.value = endKeep.value.substring(ws.length);
|
||||
}
|
||||
// otherwise we've got a deletion and no insertion
|
||||
}
|
||||
else if (startKeep && endKeep) {
|
||||
var newWsFull = (0, string_js_1.leadingWs)(endKeep.value, segmenter), _c = (0, string_js_1.leadingAndTrailingWs)(deletion.value, segmenter), delWsStart = _c[0], delWsEnd = _c[1];
|
||||
// Any whitespace that comes straight after startKeep in both the old and
|
||||
// new texts, assign to startKeep and remove from the deletion.
|
||||
var newWsStart = (0, string_js_1.longestCommonPrefix)(newWsFull, delWsStart);
|
||||
deletion.value = (0, string_js_1.removePrefix)(deletion.value, newWsStart);
|
||||
// Any whitespace that comes straight before endKeep in both the old and
|
||||
// new texts, and hasn't already been assigned to startKeep, assign to
|
||||
// endKeep and remove from the deletion.
|
||||
var newWsEnd = (0, string_js_1.longestCommonSuffix)((0, string_js_1.removePrefix)(newWsFull, newWsStart), delWsEnd);
|
||||
deletion.value = (0, string_js_1.removeSuffix)(deletion.value, newWsEnd);
|
||||
endKeep.value = (0, string_js_1.replacePrefix)(endKeep.value, newWsFull, newWsEnd);
|
||||
// If there's any whitespace from the new text that HASN'T already been
|
||||
// assigned, assign it to the start:
|
||||
startKeep.value = (0, string_js_1.replaceSuffix)(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
|
||||
}
|
||||
else if (endKeep) {
|
||||
// We are at the start of the text. Preserve all the whitespace on
|
||||
// endKeep, and just remove whitespace from the end of deletion to the
|
||||
// extent that it overlaps with the start of endKeep.
|
||||
var endKeepWsPrefix = (0, string_js_1.leadingWs)(endKeep.value, segmenter);
|
||||
var deletionWsSuffix = (0, string_js_1.trailingWs)(deletion.value, segmenter);
|
||||
var overlap = (0, string_js_1.maximumOverlap)(deletionWsSuffix, endKeepWsPrefix);
|
||||
deletion.value = (0, string_js_1.removeSuffix)(deletion.value, overlap);
|
||||
}
|
||||
else if (startKeep) {
|
||||
// We are at the END of the text. Preserve all the whitespace on
|
||||
// startKeep, and just remove whitespace from the start of deletion to
|
||||
// the extent that it overlaps with the end of startKeep.
|
||||
var startKeepWsSuffix = (0, string_js_1.trailingWs)(startKeep.value, segmenter);
|
||||
var deletionWsPrefix = (0, string_js_1.leadingWs)(deletion.value, segmenter);
|
||||
var overlap = (0, string_js_1.maximumOverlap)(startKeepWsSuffix, deletionWsPrefix);
|
||||
deletion.value = (0, string_js_1.removePrefix)(deletion.value, overlap);
|
||||
}
|
||||
}
|
||||
var WordsWithSpaceDiff = /** @class */ (function (_super) {
|
||||
__extends(WordsWithSpaceDiff, _super);
|
||||
function WordsWithSpaceDiff() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
WordsWithSpaceDiff.prototype.tokenize = function (value) {
|
||||
// Slightly different to the tokenizeIncludingWhitespace regex used above in
|
||||
// that this one treats each individual newline as a distinct token, rather
|
||||
// than merging them into other surrounding whitespace. This was requested
|
||||
// in https://github.com/kpdecker/jsdiff/issues/180 &
|
||||
// https://github.com/kpdecker/jsdiff/issues/211
|
||||
var regex = new RegExp("(\\r?\\n)|[".concat(extendedWordChars, "]+|[^\\S\\n\\r]+|[^").concat(extendedWordChars, "]"), 'ug');
|
||||
return value.match(regex) || [];
|
||||
};
|
||||
return WordsWithSpaceDiff;
|
||||
}(base_js_1.default));
|
||||
exports.wordsWithSpaceDiff = new WordsWithSpaceDiff();
|
||||
function diffWordsWithSpace(oldStr, newStr, options) {
|
||||
return exports.wordsWithSpaceDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
"use strict";
|
||||
/* See LICENSE file for terms of use */
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.canonicalize = exports.convertChangesToXML = exports.convertChangesToDMP = exports.reversePatch = exports.parsePatch = exports.applyPatches = exports.applyPatch = exports.OMIT_HEADERS = exports.FILE_HEADERS_ONLY = exports.INCLUDE_HEADERS = exports.formatPatch = exports.createPatch = exports.createTwoFilesPatch = exports.structuredPatch = exports.arrayDiff = exports.diffArrays = exports.jsonDiff = exports.diffJson = exports.cssDiff = exports.diffCss = exports.sentenceDiff = exports.diffSentences = exports.diffTrimmedLines = exports.lineDiff = exports.diffLines = exports.wordsWithSpaceDiff = exports.diffWordsWithSpace = exports.wordDiff = exports.diffWords = exports.characterDiff = exports.diffChars = exports.Diff = void 0;
|
||||
/*
|
||||
* Text diff implementation.
|
||||
*
|
||||
* This library supports the following APIs:
|
||||
* Diff.diffChars: Character by character diff
|
||||
* Diff.diffWords: Word (as defined by \b regex) diff which ignores whitespace
|
||||
* Diff.diffLines: Line based diff
|
||||
*
|
||||
* Diff.diffCss: Diff targeted at CSS content
|
||||
*
|
||||
* These methods are based on the implementation proposed in
|
||||
* "An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
|
||||
* http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
|
||||
*/
|
||||
var base_js_1 = require("./diff/base.js");
|
||||
exports.Diff = base_js_1.default;
|
||||
var character_js_1 = require("./diff/character.js");
|
||||
Object.defineProperty(exports, "diffChars", { enumerable: true, get: function () { return character_js_1.diffChars; } });
|
||||
Object.defineProperty(exports, "characterDiff", { enumerable: true, get: function () { return character_js_1.characterDiff; } });
|
||||
var word_js_1 = require("./diff/word.js");
|
||||
Object.defineProperty(exports, "diffWords", { enumerable: true, get: function () { return word_js_1.diffWords; } });
|
||||
Object.defineProperty(exports, "diffWordsWithSpace", { enumerable: true, get: function () { return word_js_1.diffWordsWithSpace; } });
|
||||
Object.defineProperty(exports, "wordDiff", { enumerable: true, get: function () { return word_js_1.wordDiff; } });
|
||||
Object.defineProperty(exports, "wordsWithSpaceDiff", { enumerable: true, get: function () { return word_js_1.wordsWithSpaceDiff; } });
|
||||
var line_js_1 = require("./diff/line.js");
|
||||
Object.defineProperty(exports, "diffLines", { enumerable: true, get: function () { return line_js_1.diffLines; } });
|
||||
Object.defineProperty(exports, "diffTrimmedLines", { enumerable: true, get: function () { return line_js_1.diffTrimmedLines; } });
|
||||
Object.defineProperty(exports, "lineDiff", { enumerable: true, get: function () { return line_js_1.lineDiff; } });
|
||||
var sentence_js_1 = require("./diff/sentence.js");
|
||||
Object.defineProperty(exports, "diffSentences", { enumerable: true, get: function () { return sentence_js_1.diffSentences; } });
|
||||
Object.defineProperty(exports, "sentenceDiff", { enumerable: true, get: function () { return sentence_js_1.sentenceDiff; } });
|
||||
var css_js_1 = require("./diff/css.js");
|
||||
Object.defineProperty(exports, "diffCss", { enumerable: true, get: function () { return css_js_1.diffCss; } });
|
||||
Object.defineProperty(exports, "cssDiff", { enumerable: true, get: function () { return css_js_1.cssDiff; } });
|
||||
var json_js_1 = require("./diff/json.js");
|
||||
Object.defineProperty(exports, "diffJson", { enumerable: true, get: function () { return json_js_1.diffJson; } });
|
||||
Object.defineProperty(exports, "canonicalize", { enumerable: true, get: function () { return json_js_1.canonicalize; } });
|
||||
Object.defineProperty(exports, "jsonDiff", { enumerable: true, get: function () { return json_js_1.jsonDiff; } });
|
||||
var array_js_1 = require("./diff/array.js");
|
||||
Object.defineProperty(exports, "diffArrays", { enumerable: true, get: function () { return array_js_1.diffArrays; } });
|
||||
Object.defineProperty(exports, "arrayDiff", { enumerable: true, get: function () { return array_js_1.arrayDiff; } });
|
||||
var apply_js_1 = require("./patch/apply.js");
|
||||
Object.defineProperty(exports, "applyPatch", { enumerable: true, get: function () { return apply_js_1.applyPatch; } });
|
||||
Object.defineProperty(exports, "applyPatches", { enumerable: true, get: function () { return apply_js_1.applyPatches; } });
|
||||
var parse_js_1 = require("./patch/parse.js");
|
||||
Object.defineProperty(exports, "parsePatch", { enumerable: true, get: function () { return parse_js_1.parsePatch; } });
|
||||
var reverse_js_1 = require("./patch/reverse.js");
|
||||
Object.defineProperty(exports, "reversePatch", { enumerable: true, get: function () { return reverse_js_1.reversePatch; } });
|
||||
var create_js_1 = require("./patch/create.js");
|
||||
Object.defineProperty(exports, "structuredPatch", { enumerable: true, get: function () { return create_js_1.structuredPatch; } });
|
||||
Object.defineProperty(exports, "createTwoFilesPatch", { enumerable: true, get: function () { return create_js_1.createTwoFilesPatch; } });
|
||||
Object.defineProperty(exports, "createPatch", { enumerable: true, get: function () { return create_js_1.createPatch; } });
|
||||
Object.defineProperty(exports, "formatPatch", { enumerable: true, get: function () { return create_js_1.formatPatch; } });
|
||||
Object.defineProperty(exports, "INCLUDE_HEADERS", { enumerable: true, get: function () { return create_js_1.INCLUDE_HEADERS; } });
|
||||
Object.defineProperty(exports, "FILE_HEADERS_ONLY", { enumerable: true, get: function () { return create_js_1.FILE_HEADERS_ONLY; } });
|
||||
Object.defineProperty(exports, "OMIT_HEADERS", { enumerable: true, get: function () { return create_js_1.OMIT_HEADERS; } });
|
||||
var dmp_js_1 = require("./convert/dmp.js");
|
||||
Object.defineProperty(exports, "convertChangesToDMP", { enumerable: true, get: function () { return dmp_js_1.convertChangesToDMP; } });
|
||||
var xml_js_1 = require("./convert/xml.js");
|
||||
Object.defineProperty(exports, "convertChangesToXML", { enumerable: true, get: function () { return xml_js_1.convertChangesToXML; } });
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"type":"commonjs","sideEffects":false}
|
||||
Generated
Vendored
+267
@@ -0,0 +1,267 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.applyPatch = applyPatch;
|
||||
exports.applyPatches = applyPatches;
|
||||
var string_js_1 = require("../util/string.js");
|
||||
var line_endings_js_1 = require("./line-endings.js");
|
||||
var parse_js_1 = require("./parse.js");
|
||||
var distance_iterator_js_1 = require("../util/distance-iterator.js");
|
||||
/**
|
||||
* attempts to apply a unified diff patch.
|
||||
*
|
||||
* Hunks are applied first to last.
|
||||
* `applyPatch` first tries to apply the first hunk at the line number specified in the hunk header, and with all context lines matching exactly.
|
||||
* If that fails, it tries scanning backwards and forwards, one line at a time, to find a place to apply the hunk where the context lines match exactly.
|
||||
* If that still fails, and `fuzzFactor` is greater than zero, it increments the maximum number of mismatches (missing, extra, or changed context lines) that there can be between the hunk context and a region where we are trying to apply the patch such that the hunk will still be considered to match.
|
||||
* Regardless of `fuzzFactor`, lines to be deleted in the hunk *must* be present for a hunk to match, and the context lines *immediately* before and after an insertion must match exactly.
|
||||
*
|
||||
* Once a hunk is successfully fitted, the process begins again with the next hunk.
|
||||
* Regardless of `fuzzFactor`, later hunks must be applied later in the file than earlier hunks.
|
||||
*
|
||||
* If a hunk cannot be successfully fitted *anywhere* with fewer than `fuzzFactor` mismatches, `applyPatch` fails and returns `false`.
|
||||
*
|
||||
* If a hunk is successfully fitted but not at the line number specified by the hunk header, all subsequent hunks have their target line number adjusted accordingly.
|
||||
* (e.g. if the first hunk is applied 10 lines below where the hunk header said it should fit, `applyPatch` will *start* looking for somewhere to apply the second hunk 10 lines below where its hunk header says it goes.)
|
||||
*
|
||||
* If the patch was applied successfully, returns a string containing the patched text.
|
||||
* If the patch could not be applied (because some hunks in the patch couldn't be fitted to the text in `source`), `applyPatch` returns false.
|
||||
*
|
||||
* @param patch a string diff or the output from the `parsePatch` or `structuredPatch` methods.
|
||||
*/
|
||||
function applyPatch(source, patch, options) {
|
||||
if (options === void 0) { options = {}; }
|
||||
var patches;
|
||||
if (typeof patch === 'string') {
|
||||
patches = (0, parse_js_1.parsePatch)(patch);
|
||||
}
|
||||
else if (Array.isArray(patch)) {
|
||||
patches = patch;
|
||||
}
|
||||
else {
|
||||
patches = [patch];
|
||||
}
|
||||
if (patches.length > 1) {
|
||||
throw new Error('applyPatch only works with a single input.');
|
||||
}
|
||||
return applyStructuredPatch(source, patches[0], options);
|
||||
}
|
||||
function applyStructuredPatch(source, patch, options) {
|
||||
if (options === void 0) { options = {}; }
|
||||
if (options.autoConvertLineEndings || options.autoConvertLineEndings == null) {
|
||||
if ((0, string_js_1.hasOnlyWinLineEndings)(source) && (0, line_endings_js_1.isUnix)(patch)) {
|
||||
patch = (0, line_endings_js_1.unixToWin)(patch);
|
||||
}
|
||||
else if ((0, string_js_1.hasOnlyUnixLineEndings)(source) && (0, line_endings_js_1.isWin)(patch)) {
|
||||
patch = (0, line_endings_js_1.winToUnix)(patch);
|
||||
}
|
||||
}
|
||||
// Apply the diff to the input
|
||||
var lines = source.split('\n'), hunks = patch.hunks, compareLine = options.compareLine || (function (lineNumber, line, operation, patchContent) { return line === patchContent; }), fuzzFactor = options.fuzzFactor || 0;
|
||||
var minLine = 0;
|
||||
if (fuzzFactor < 0 || !Number.isInteger(fuzzFactor)) {
|
||||
throw new Error('fuzzFactor must be a non-negative integer');
|
||||
}
|
||||
// Special case for empty patch.
|
||||
if (!hunks.length) {
|
||||
return source;
|
||||
}
|
||||
// Before anything else, handle EOFNL insertion/removal. If the patch tells us to make a change
|
||||
// to the EOFNL that is redundant/impossible - i.e. to remove a newline that's not there, or add a
|
||||
// newline that already exists - then we either return false and fail to apply the patch (if
|
||||
// fuzzFactor is 0) or simply ignore the problem and do nothing (if fuzzFactor is >0).
|
||||
// If we do need to remove/add a newline at EOF, this will always be in the final hunk:
|
||||
var prevLine = '', removeEOFNL = false, addEOFNL = false;
|
||||
for (var i = 0; i < hunks[hunks.length - 1].lines.length; i++) {
|
||||
var line = hunks[hunks.length - 1].lines[i];
|
||||
if (line[0] == '\\') {
|
||||
if (prevLine[0] == '+') {
|
||||
removeEOFNL = true;
|
||||
}
|
||||
else if (prevLine[0] == '-') {
|
||||
addEOFNL = true;
|
||||
}
|
||||
}
|
||||
prevLine = line;
|
||||
}
|
||||
if (removeEOFNL) {
|
||||
if (addEOFNL) {
|
||||
// This means the final line gets changed but doesn't have a trailing newline in either the
|
||||
// original or patched version. In that case, we do nothing if fuzzFactor > 0, and if
|
||||
// fuzzFactor is 0, we simply validate that the source file has no trailing newline.
|
||||
if (!fuzzFactor && lines[lines.length - 1] == '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (lines[lines.length - 1] == '') {
|
||||
lines.pop();
|
||||
}
|
||||
else if (!fuzzFactor) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (addEOFNL) {
|
||||
if (lines[lines.length - 1] != '') {
|
||||
lines.push('');
|
||||
}
|
||||
else if (!fuzzFactor) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks if the hunk can be made to fit at the provided location with at most `maxErrors`
|
||||
* insertions, substitutions, or deletions, while ensuring also that:
|
||||
* - lines deleted in the hunk match exactly, and
|
||||
* - wherever an insertion operation or block of insertion operations appears in the hunk, the
|
||||
* immediately preceding and following lines of context match exactly
|
||||
*
|
||||
* `toPos` should be set such that lines[toPos] is meant to match hunkLines[0].
|
||||
*
|
||||
* If the hunk can be applied, returns an object with properties `oldLineLastI` and
|
||||
* `replacementLines`. Otherwise, returns null.
|
||||
*/
|
||||
function applyHunk(hunkLines, toPos, maxErrors, hunkLinesI, lastContextLineMatched, patchedLines, patchedLinesLength) {
|
||||
if (hunkLinesI === void 0) { hunkLinesI = 0; }
|
||||
if (lastContextLineMatched === void 0) { lastContextLineMatched = true; }
|
||||
if (patchedLines === void 0) { patchedLines = []; }
|
||||
if (patchedLinesLength === void 0) { patchedLinesLength = 0; }
|
||||
var nConsecutiveOldContextLines = 0;
|
||||
var nextContextLineMustMatch = false;
|
||||
for (; hunkLinesI < hunkLines.length; hunkLinesI++) {
|
||||
var hunkLine = hunkLines[hunkLinesI], operation = (hunkLine.length > 0 ? hunkLine[0] : ' '), content = (hunkLine.length > 0 ? hunkLine.substr(1) : hunkLine);
|
||||
if (operation === '-') {
|
||||
if (compareLine(toPos + 1, lines[toPos], operation, content)) {
|
||||
toPos++;
|
||||
nConsecutiveOldContextLines = 0;
|
||||
}
|
||||
else {
|
||||
if (!maxErrors || lines[toPos] == null) {
|
||||
return null;
|
||||
}
|
||||
patchedLines[patchedLinesLength] = lines[toPos];
|
||||
return applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1);
|
||||
}
|
||||
}
|
||||
if (operation === '+') {
|
||||
if (!lastContextLineMatched) {
|
||||
return null;
|
||||
}
|
||||
patchedLines[patchedLinesLength] = content;
|
||||
patchedLinesLength++;
|
||||
nConsecutiveOldContextLines = 0;
|
||||
nextContextLineMustMatch = true;
|
||||
}
|
||||
if (operation === ' ') {
|
||||
nConsecutiveOldContextLines++;
|
||||
patchedLines[patchedLinesLength] = lines[toPos];
|
||||
if (compareLine(toPos + 1, lines[toPos], operation, content)) {
|
||||
patchedLinesLength++;
|
||||
lastContextLineMatched = true;
|
||||
nextContextLineMustMatch = false;
|
||||
toPos++;
|
||||
}
|
||||
else {
|
||||
if (nextContextLineMustMatch || !maxErrors) {
|
||||
return null;
|
||||
}
|
||||
// Consider 3 possibilities in sequence:
|
||||
// 1. lines contains a *substitution* not included in the patch context, or
|
||||
// 2. lines contains an *insertion* not included in the patch context, or
|
||||
// 3. lines contains a *deletion* not included in the patch context
|
||||
// The first two options are of course only possible if the line from lines is non-null -
|
||||
// i.e. only option 3 is possible if we've overrun the end of the old file.
|
||||
return (lines[toPos] && (applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength + 1) || applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1)) || applyHunk(hunkLines, toPos, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Before returning, trim any unmodified context lines off the end of patchedLines and reduce
|
||||
// toPos (and thus oldLineLastI) accordingly. This allows later hunks to be applied to a region
|
||||
// that starts in this hunk's trailing context.
|
||||
patchedLinesLength -= nConsecutiveOldContextLines;
|
||||
toPos -= nConsecutiveOldContextLines;
|
||||
patchedLines.length = patchedLinesLength;
|
||||
return {
|
||||
patchedLines: patchedLines,
|
||||
oldLineLastI: toPos - 1
|
||||
};
|
||||
}
|
||||
var resultLines = [];
|
||||
// Search best fit offsets for each hunk based on the previous ones
|
||||
var prevHunkOffset = 0;
|
||||
for (var i = 0; i < hunks.length; i++) {
|
||||
var hunk = hunks[i];
|
||||
var hunkResult = void 0;
|
||||
var maxLine = lines.length - hunk.oldLines + fuzzFactor;
|
||||
var toPos = void 0;
|
||||
for (var maxErrors = 0; maxErrors <= fuzzFactor; maxErrors++) {
|
||||
toPos = hunk.oldStart + prevHunkOffset - 1;
|
||||
var iterator = (0, distance_iterator_js_1.default)(toPos, minLine, maxLine);
|
||||
for (; toPos !== undefined; toPos = iterator()) {
|
||||
hunkResult = applyHunk(hunk.lines, toPos, maxErrors);
|
||||
if (hunkResult) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hunkResult) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hunkResult) {
|
||||
return false;
|
||||
}
|
||||
// Copy everything from the end of where we applied the last hunk to the start of this hunk
|
||||
for (var i_1 = minLine; i_1 < toPos; i_1++) {
|
||||
resultLines.push(lines[i_1]);
|
||||
}
|
||||
// Add the lines produced by applying the hunk:
|
||||
for (var i_2 = 0; i_2 < hunkResult.patchedLines.length; i_2++) {
|
||||
var line = hunkResult.patchedLines[i_2];
|
||||
resultLines.push(line);
|
||||
}
|
||||
// Set lower text limit to end of the current hunk, so next ones don't try
|
||||
// to fit over already patched text
|
||||
minLine = hunkResult.oldLineLastI + 1;
|
||||
// Note the offset between where the patch said the hunk should've applied and where we
|
||||
// applied it, so we can adjust future hunks accordingly:
|
||||
prevHunkOffset = toPos + 1 - hunk.oldStart;
|
||||
}
|
||||
// Copy over the rest of the lines from the old text
|
||||
for (var i = minLine; i < lines.length; i++) {
|
||||
resultLines.push(lines[i]);
|
||||
}
|
||||
return resultLines.join('\n');
|
||||
}
|
||||
/**
|
||||
* applies one or more patches.
|
||||
*
|
||||
* `patch` may be either an array of structured patch objects, or a string representing a patch in unified diff format (which may patch one or more files).
|
||||
*
|
||||
* This method will iterate over the contents of the patch and apply to data provided through callbacks. The general flow for each patch index is:
|
||||
*
|
||||
* - `options.loadFile(index, callback)` is called. The caller should then load the contents of the file and then pass that to the `callback(err, data)` callback. Passing an `err` will terminate further patch execution.
|
||||
* - `options.patched(index, content, callback)` is called once the patch has been applied. `content` will be the return value from `applyPatch`. When it's ready, the caller should call `callback(err)` callback. Passing an `err` will terminate further patch execution.
|
||||
*
|
||||
* Once all patches have been applied or an error occurs, the `options.complete(err)` callback is made.
|
||||
*/
|
||||
function applyPatches(uniDiff, options) {
|
||||
var spDiff = typeof uniDiff === 'string' ? (0, parse_js_1.parsePatch)(uniDiff) : uniDiff;
|
||||
var currentIndex = 0;
|
||||
function processIndex() {
|
||||
var index = spDiff[currentIndex++];
|
||||
if (!index) {
|
||||
return options.complete();
|
||||
}
|
||||
options.loadFile(index, function (err, data) {
|
||||
if (err) {
|
||||
return options.complete(err);
|
||||
}
|
||||
var updatedContent = applyPatch(data, index, options);
|
||||
options.patched(index, updatedContent, function (err) {
|
||||
if (err) {
|
||||
return options.complete(err);
|
||||
}
|
||||
processIndex();
|
||||
});
|
||||
});
|
||||
}
|
||||
processIndex();
|
||||
}
|
||||
Generated
Vendored
+251
@@ -0,0 +1,251 @@
|
||||
"use strict";
|
||||
var __assign = (this && this.__assign) || function () {
|
||||
__assign = Object.assign || function(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
return __assign.apply(this, arguments);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.OMIT_HEADERS = exports.FILE_HEADERS_ONLY = exports.INCLUDE_HEADERS = void 0;
|
||||
exports.structuredPatch = structuredPatch;
|
||||
exports.formatPatch = formatPatch;
|
||||
exports.createTwoFilesPatch = createTwoFilesPatch;
|
||||
exports.createPatch = createPatch;
|
||||
var line_js_1 = require("../diff/line.js");
|
||||
exports.INCLUDE_HEADERS = {
|
||||
includeIndex: true,
|
||||
includeUnderline: true,
|
||||
includeFileHeaders: true
|
||||
};
|
||||
exports.FILE_HEADERS_ONLY = {
|
||||
includeIndex: false,
|
||||
includeUnderline: false,
|
||||
includeFileHeaders: true
|
||||
};
|
||||
exports.OMIT_HEADERS = {
|
||||
includeIndex: false,
|
||||
includeUnderline: false,
|
||||
includeFileHeaders: false
|
||||
};
|
||||
function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
|
||||
var optionsObj;
|
||||
if (!options) {
|
||||
optionsObj = {};
|
||||
}
|
||||
else if (typeof options === 'function') {
|
||||
optionsObj = { callback: options };
|
||||
}
|
||||
else {
|
||||
optionsObj = options;
|
||||
}
|
||||
if (typeof optionsObj.context === 'undefined') {
|
||||
optionsObj.context = 4;
|
||||
}
|
||||
// We copy this into its own variable to placate TypeScript, which thinks
|
||||
// optionsObj.context might be undefined in the callbacks below.
|
||||
var context = optionsObj.context;
|
||||
// @ts-expect-error (runtime check for something that is correctly a static type error)
|
||||
if (optionsObj.newlineIsToken) {
|
||||
throw new Error('newlineIsToken may not be used with patch-generation functions, only with diffing functions');
|
||||
}
|
||||
if (!optionsObj.callback) {
|
||||
return diffLinesResultToPatch((0, line_js_1.diffLines)(oldStr, newStr, optionsObj));
|
||||
}
|
||||
else {
|
||||
var callback_1 = optionsObj.callback;
|
||||
(0, line_js_1.diffLines)(oldStr, newStr, __assign(__assign({}, optionsObj), { callback: function (diff) {
|
||||
var patch = diffLinesResultToPatch(diff);
|
||||
// TypeScript is unhappy without the cast because it does not understand that `patch` may
|
||||
// be undefined here only if `callback` is StructuredPatchCallbackAbortable:
|
||||
callback_1(patch);
|
||||
} }));
|
||||
}
|
||||
function diffLinesResultToPatch(diff) {
|
||||
// STEP 1: Build up the patch with no "\ No newline at end of file" lines and with the arrays
|
||||
// of lines containing trailing newline characters. We'll tidy up later...
|
||||
if (!diff) {
|
||||
return;
|
||||
}
|
||||
diff.push({ value: '', lines: [] }); // Append an empty value to make cleanup easier
|
||||
function contextLines(lines) {
|
||||
return lines.map(function (entry) { return ' ' + entry; });
|
||||
}
|
||||
var hunks = [];
|
||||
var oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1;
|
||||
for (var i = 0; i < diff.length; i++) {
|
||||
var current = diff[i], lines = current.lines || splitLines(current.value);
|
||||
current.lines = lines;
|
||||
if (current.added || current.removed) {
|
||||
// If we have previous context, start with that
|
||||
if (!oldRangeStart) {
|
||||
var prev = diff[i - 1];
|
||||
oldRangeStart = oldLine;
|
||||
newRangeStart = newLine;
|
||||
if (prev) {
|
||||
curRange = context > 0 ? contextLines(prev.lines.slice(-context)) : [];
|
||||
oldRangeStart -= curRange.length;
|
||||
newRangeStart -= curRange.length;
|
||||
}
|
||||
}
|
||||
// Output our changes
|
||||
for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) {
|
||||
var line = lines_1[_i];
|
||||
curRange.push((current.added ? '+' : '-') + line);
|
||||
}
|
||||
// Track the updated file position
|
||||
if (current.added) {
|
||||
newLine += lines.length;
|
||||
}
|
||||
else {
|
||||
oldLine += lines.length;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Identical context lines. Track line changes
|
||||
if (oldRangeStart) {
|
||||
// Close out any changes that have been output (or join overlapping)
|
||||
if (lines.length <= context * 2 && i < diff.length - 2) {
|
||||
// Overlapping
|
||||
for (var _a = 0, _b = contextLines(lines); _a < _b.length; _a++) {
|
||||
var line = _b[_a];
|
||||
curRange.push(line);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// end the range and output
|
||||
var contextSize = Math.min(lines.length, context);
|
||||
for (var _c = 0, _d = contextLines(lines.slice(0, contextSize)); _c < _d.length; _c++) {
|
||||
var line = _d[_c];
|
||||
curRange.push(line);
|
||||
}
|
||||
var hunk = {
|
||||
oldStart: oldRangeStart,
|
||||
oldLines: (oldLine - oldRangeStart + contextSize),
|
||||
newStart: newRangeStart,
|
||||
newLines: (newLine - newRangeStart + contextSize),
|
||||
lines: curRange
|
||||
};
|
||||
hunks.push(hunk);
|
||||
oldRangeStart = 0;
|
||||
newRangeStart = 0;
|
||||
curRange = [];
|
||||
}
|
||||
}
|
||||
oldLine += lines.length;
|
||||
newLine += lines.length;
|
||||
}
|
||||
}
|
||||
// Step 2: eliminate the trailing `\n` from each line of each hunk, and, where needed, add
|
||||
// "\ No newline at end of file".
|
||||
for (var _e = 0, hunks_1 = hunks; _e < hunks_1.length; _e++) {
|
||||
var hunk = hunks_1[_e];
|
||||
for (var i = 0; i < hunk.lines.length; i++) {
|
||||
if (hunk.lines[i].endsWith('\n')) {
|
||||
hunk.lines[i] = hunk.lines[i].slice(0, -1);
|
||||
}
|
||||
else {
|
||||
hunk.lines.splice(i + 1, 0, '\\ No newline at end of file');
|
||||
i++; // Skip the line we just added, then continue iterating
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
oldFileName: oldFileName, newFileName: newFileName,
|
||||
oldHeader: oldHeader, newHeader: newHeader,
|
||||
hunks: hunks
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* creates a unified diff patch.
|
||||
* @param patch either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`)
|
||||
*/
|
||||
function formatPatch(patch, headerOptions) {
|
||||
if (!headerOptions) {
|
||||
headerOptions = exports.INCLUDE_HEADERS;
|
||||
}
|
||||
if (Array.isArray(patch)) {
|
||||
if (patch.length > 1 && !headerOptions.includeFileHeaders) {
|
||||
throw new Error('Cannot omit file headers on a multi-file patch. '
|
||||
+ '(The result would be unparseable; how would a tool trying to apply '
|
||||
+ 'the patch know which changes are to which file?)');
|
||||
}
|
||||
return patch.map(function (p) { return formatPatch(p, headerOptions); }).join('\n');
|
||||
}
|
||||
var ret = [];
|
||||
if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName) {
|
||||
ret.push('Index: ' + patch.oldFileName);
|
||||
}
|
||||
if (headerOptions.includeUnderline) {
|
||||
ret.push('===================================================================');
|
||||
}
|
||||
if (headerOptions.includeFileHeaders) {
|
||||
ret.push('--- ' + patch.oldFileName + (typeof patch.oldHeader === 'undefined' ? '' : '\t' + patch.oldHeader));
|
||||
ret.push('+++ ' + patch.newFileName + (typeof patch.newHeader === 'undefined' ? '' : '\t' + patch.newHeader));
|
||||
}
|
||||
for (var i = 0; i < patch.hunks.length; i++) {
|
||||
var hunk = patch.hunks[i];
|
||||
// Unified Diff Format quirk: If the chunk size is 0,
|
||||
// the first number is one lower than one would expect.
|
||||
// https://www.artima.com/weblogs/viewpost.jsp?thread=164293
|
||||
if (hunk.oldLines === 0) {
|
||||
hunk.oldStart -= 1;
|
||||
}
|
||||
if (hunk.newLines === 0) {
|
||||
hunk.newStart -= 1;
|
||||
}
|
||||
ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines
|
||||
+ ' +' + hunk.newStart + ',' + hunk.newLines
|
||||
+ ' @@');
|
||||
for (var _i = 0, _a = hunk.lines; _i < _a.length; _i++) {
|
||||
var line = _a[_i];
|
||||
ret.push(line);
|
||||
}
|
||||
}
|
||||
return ret.join('\n') + '\n';
|
||||
}
|
||||
function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
|
||||
if (typeof options === 'function') {
|
||||
options = { callback: options };
|
||||
}
|
||||
if (!(options === null || options === void 0 ? void 0 : options.callback)) {
|
||||
var patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options);
|
||||
if (!patchObj) {
|
||||
return;
|
||||
}
|
||||
return formatPatch(patchObj, options === null || options === void 0 ? void 0 : options.headerOptions);
|
||||
}
|
||||
else {
|
||||
var callback_2 = options.callback;
|
||||
structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, __assign(__assign({}, options), { callback: function (patchObj) {
|
||||
if (!patchObj) {
|
||||
callback_2(undefined);
|
||||
}
|
||||
else {
|
||||
callback_2(formatPatch(patchObj, options.headerOptions));
|
||||
}
|
||||
} }));
|
||||
}
|
||||
}
|
||||
function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) {
|
||||
return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options);
|
||||
}
|
||||
/**
|
||||
* Split `text` into an array of lines, including the trailing newline character (where present)
|
||||
*/
|
||||
function splitLines(text) {
|
||||
var hasTrailingNl = text.endsWith('\n');
|
||||
var result = text.split('\n').map(function (line) { return line + '\n'; });
|
||||
if (hasTrailingNl) {
|
||||
result.pop();
|
||||
}
|
||||
else {
|
||||
result.push(result.pop().slice(0, -1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Generated
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
"use strict";
|
||||
var __assign = (this && this.__assign) || function () {
|
||||
__assign = Object.assign || function(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
return __assign.apply(this, arguments);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.unixToWin = unixToWin;
|
||||
exports.winToUnix = winToUnix;
|
||||
exports.isUnix = isUnix;
|
||||
exports.isWin = isWin;
|
||||
function unixToWin(patch) {
|
||||
if (Array.isArray(patch)) {
|
||||
// It would be cleaner if instead of the line below we could just write
|
||||
// return patch.map(unixToWin)
|
||||
// but mysteriously TypeScript (v5.7.3 at the time of writing) does not like this and it will
|
||||
// refuse to compile, thinking that unixToWin could then return StructuredPatch[][] and the
|
||||
// result would be incompatible with the overload signatures.
|
||||
// See bug report at https://github.com/microsoft/TypeScript/issues/61398.
|
||||
return patch.map(function (p) { return unixToWin(p); });
|
||||
}
|
||||
return __assign(__assign({}, patch), { hunks: patch.hunks.map(function (hunk) { return (__assign(__assign({}, hunk), { lines: hunk.lines.map(function (line, i) {
|
||||
var _a;
|
||||
return (line.startsWith('\\') || line.endsWith('\r') || ((_a = hunk.lines[i + 1]) === null || _a === void 0 ? void 0 : _a.startsWith('\\')))
|
||||
? line
|
||||
: line + '\r';
|
||||
}) })); }) });
|
||||
}
|
||||
function winToUnix(patch) {
|
||||
if (Array.isArray(patch)) {
|
||||
// (See comment above equivalent line in unixToWin)
|
||||
return patch.map(function (p) { return winToUnix(p); });
|
||||
}
|
||||
return __assign(__assign({}, patch), { hunks: patch.hunks.map(function (hunk) { return (__assign(__assign({}, hunk), { lines: hunk.lines.map(function (line) { return line.endsWith('\r') ? line.substring(0, line.length - 1) : line; }) })); }) });
|
||||
}
|
||||
/**
|
||||
* Returns true if the patch consistently uses Unix line endings (or only involves one line and has
|
||||
* no line endings).
|
||||
*/
|
||||
function isUnix(patch) {
|
||||
if (!Array.isArray(patch)) {
|
||||
patch = [patch];
|
||||
}
|
||||
return !patch.some(function (index) { return index.hunks.some(function (hunk) { return hunk.lines.some(function (line) { return !line.startsWith('\\') && line.endsWith('\r'); }); }); });
|
||||
}
|
||||
/**
|
||||
* Returns true if the patch uses Windows line endings and only Windows line endings.
|
||||
*/
|
||||
function isWin(patch) {
|
||||
if (!Array.isArray(patch)) {
|
||||
patch = [patch];
|
||||
}
|
||||
return patch.some(function (index) { return index.hunks.some(function (hunk) { return hunk.lines.some(function (line) { return line.endsWith('\r'); }); }); })
|
||||
&& patch.every(function (index) { return index.hunks.every(function (hunk) { return hunk.lines.every(function (line, i) { var _a; return line.startsWith('\\') || line.endsWith('\r') || ((_a = hunk.lines[i + 1]) === null || _a === void 0 ? void 0 : _a.startsWith('\\')); }); }); });
|
||||
}
|
||||
Generated
Vendored
+150
@@ -0,0 +1,150 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.parsePatch = parsePatch;
|
||||
/**
|
||||
* Parses a patch into structured data, in the same structure returned by `structuredPatch`.
|
||||
*
|
||||
* @return a JSON object representation of the a patch, suitable for use with the `applyPatch` method.
|
||||
*/
|
||||
function parsePatch(uniDiff) {
|
||||
var diffstr = uniDiff.split(/\n/), list = [];
|
||||
var i = 0;
|
||||
function parseIndex() {
|
||||
var index = {};
|
||||
list.push(index);
|
||||
// Parse diff metadata
|
||||
while (i < diffstr.length) {
|
||||
var line = diffstr[i];
|
||||
// File header found, end parsing diff metadata
|
||||
if ((/^(---|\+\+\+|@@)\s/).test(line)) {
|
||||
break;
|
||||
}
|
||||
// Try to parse the line as a diff header, like
|
||||
// Index: README.md
|
||||
// or
|
||||
// diff -r 9117c6561b0b -r 273ce12ad8f1 .hgignore
|
||||
// or
|
||||
// Index: something with multiple words
|
||||
// and extract the filename (or whatever else is used as an index name)
|
||||
// from the end (i.e. 'README.md', '.hgignore', or
|
||||
// 'something with multiple words' in the examples above).
|
||||
//
|
||||
// TODO: It seems awkward that we indiscriminately trim off trailing
|
||||
// whitespace here. Theoretically, couldn't that be meaningful -
|
||||
// e.g. if the patch represents a diff of a file whose name ends
|
||||
// with a space? Seems wrong to nuke it.
|
||||
// But this behaviour has been around since v2.2.1 in 2015, so if
|
||||
// it's going to change, it should be done cautiously and in a new
|
||||
// major release, for backwards-compat reasons.
|
||||
// -- ExplodingCabbage
|
||||
var headerMatch = (/^(?:Index:|diff(?: -r \w+)+)\s+/).exec(line);
|
||||
if (headerMatch) {
|
||||
index.index = line.substring(headerMatch[0].length).trim();
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// Parse file headers if they are defined. Unified diff requires them, but
|
||||
// there's no technical issues to have an isolated hunk without file header
|
||||
parseFileHeader(index);
|
||||
parseFileHeader(index);
|
||||
// Parse hunks
|
||||
index.hunks = [];
|
||||
while (i < diffstr.length) {
|
||||
var line = diffstr[i];
|
||||
if ((/^(Index:\s|diff\s|---\s|\+\+\+\s|===================================================================)/).test(line)) {
|
||||
break;
|
||||
}
|
||||
else if ((/^@@/).test(line)) {
|
||||
index.hunks.push(parseHunk());
|
||||
}
|
||||
else if (line) {
|
||||
throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(line));
|
||||
}
|
||||
else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parses the --- and +++ headers, if none are found, no lines
|
||||
// are consumed.
|
||||
function parseFileHeader(index) {
|
||||
var fileHeaderMatch = (/^(---|\+\+\+)\s+/).exec(diffstr[i]);
|
||||
if (fileHeaderMatch) {
|
||||
var prefix = fileHeaderMatch[1], data = diffstr[i].substring(3).trim().split('\t', 2), header = (data[1] || '').trim();
|
||||
var fileName = data[0].replace(/\\\\/g, '\\');
|
||||
if (fileName.startsWith('"') && fileName.endsWith('"')) {
|
||||
fileName = fileName.substr(1, fileName.length - 2);
|
||||
}
|
||||
if (prefix === '---') {
|
||||
index.oldFileName = fileName;
|
||||
index.oldHeader = header;
|
||||
}
|
||||
else {
|
||||
index.newFileName = fileName;
|
||||
index.newHeader = header;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
// Parses a hunk
|
||||
// This assumes that we are at the start of a hunk.
|
||||
function parseHunk() {
|
||||
var _a;
|
||||
var chunkHeaderIndex = i, chunkHeaderLine = diffstr[i++], chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
|
||||
var hunk = {
|
||||
oldStart: +chunkHeader[1],
|
||||
oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2],
|
||||
newStart: +chunkHeader[3],
|
||||
newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4],
|
||||
lines: []
|
||||
};
|
||||
// Unified Diff Format quirk: If the chunk size is 0,
|
||||
// the first number is one lower than one would expect.
|
||||
// https://www.artima.com/weblogs/viewpost.jsp?thread=164293
|
||||
if (hunk.oldLines === 0) {
|
||||
hunk.oldStart += 1;
|
||||
}
|
||||
if (hunk.newLines === 0) {
|
||||
hunk.newStart += 1;
|
||||
}
|
||||
var addCount = 0, removeCount = 0;
|
||||
for (; i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || ((_a = diffstr[i]) === null || _a === void 0 ? void 0 : _a.startsWith('\\'))); i++) {
|
||||
var operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0];
|
||||
if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
|
||||
hunk.lines.push(diffstr[i]);
|
||||
if (operation === '+') {
|
||||
addCount++;
|
||||
}
|
||||
else if (operation === '-') {
|
||||
removeCount++;
|
||||
}
|
||||
else if (operation === ' ') {
|
||||
addCount++;
|
||||
removeCount++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error("Hunk at line ".concat(chunkHeaderIndex + 1, " contained invalid line ").concat(diffstr[i]));
|
||||
}
|
||||
}
|
||||
// Handle the empty block count case
|
||||
if (!addCount && hunk.newLines === 1) {
|
||||
hunk.newLines = 0;
|
||||
}
|
||||
if (!removeCount && hunk.oldLines === 1) {
|
||||
hunk.oldLines = 0;
|
||||
}
|
||||
// Perform sanity checking
|
||||
if (addCount !== hunk.newLines) {
|
||||
throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
|
||||
}
|
||||
if (removeCount !== hunk.oldLines) {
|
||||
throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
|
||||
}
|
||||
return hunk;
|
||||
}
|
||||
while (i < diffstr.length) {
|
||||
parseIndex();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
Generated
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
"use strict";
|
||||
var __assign = (this && this.__assign) || function () {
|
||||
__assign = Object.assign || function(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
return __assign.apply(this, arguments);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.reversePatch = reversePatch;
|
||||
function reversePatch(structuredPatch) {
|
||||
if (Array.isArray(structuredPatch)) {
|
||||
// (See comment in unixToWin for why we need the pointless-looking anonymous function here)
|
||||
return structuredPatch.map(function (patch) { return reversePatch(patch); }).reverse();
|
||||
}
|
||||
return __assign(__assign({}, structuredPatch), { oldFileName: structuredPatch.newFileName, oldHeader: structuredPatch.newHeader, newFileName: structuredPatch.oldFileName, newHeader: structuredPatch.oldHeader, hunks: structuredPatch.hunks.map(function (hunk) {
|
||||
return {
|
||||
oldLines: hunk.newLines,
|
||||
oldStart: hunk.newStart,
|
||||
newLines: hunk.oldLines,
|
||||
newStart: hunk.oldStart,
|
||||
lines: hunk.lines.map(function (l) {
|
||||
if (l.startsWith('-')) {
|
||||
return "+".concat(l.slice(1));
|
||||
}
|
||||
if (l.startsWith('+')) {
|
||||
return "-".concat(l.slice(1));
|
||||
}
|
||||
return l;
|
||||
})
|
||||
};
|
||||
}) });
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.arrayEqual = arrayEqual;
|
||||
exports.arrayStartsWith = arrayStartsWith;
|
||||
function arrayEqual(a, b) {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
return arrayStartsWith(a, b);
|
||||
}
|
||||
function arrayStartsWith(array, start) {
|
||||
if (start.length > array.length) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < start.length; i++) {
|
||||
if (start[i] !== array[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Generated
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.default = default_1;
|
||||
// Iterator that traverses in the range of [min, max], stepping
|
||||
// by distance from a given start position. I.e. for [0, 4], with
|
||||
// start of 2, this will iterate 2, 3, 1, 4, 0.
|
||||
function default_1(start, minLine, maxLine) {
|
||||
var wantForward = true, backwardExhausted = false, forwardExhausted = false, localOffset = 1;
|
||||
return function iterator() {
|
||||
if (wantForward && !forwardExhausted) {
|
||||
if (backwardExhausted) {
|
||||
localOffset++;
|
||||
}
|
||||
else {
|
||||
wantForward = false;
|
||||
}
|
||||
// Check if trying to fit beyond text length, and if not, check it fits
|
||||
// after offset location (or desired location on first iteration)
|
||||
if (start + localOffset <= maxLine) {
|
||||
return start + localOffset;
|
||||
}
|
||||
forwardExhausted = true;
|
||||
}
|
||||
if (!backwardExhausted) {
|
||||
if (!forwardExhausted) {
|
||||
wantForward = true;
|
||||
}
|
||||
// Check if trying to fit before text beginning, and if not, check it fits
|
||||
// before offset location
|
||||
if (minLine <= start - localOffset) {
|
||||
return start - localOffset++;
|
||||
}
|
||||
backwardExhausted = true;
|
||||
return iterator();
|
||||
}
|
||||
// We tried to fit hunk before text beginning and beyond text length, then
|
||||
// hunk can't fit on the text. Return undefined
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.generateOptions = generateOptions;
|
||||
function generateOptions(options, defaults) {
|
||||
if (typeof options === 'function') {
|
||||
defaults.callback = options;
|
||||
}
|
||||
else if (options) {
|
||||
for (var name in options) {
|
||||
/* istanbul ignore else */
|
||||
if (Object.prototype.hasOwnProperty.call(options, name)) {
|
||||
defaults[name] = options[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaults;
|
||||
}
|
||||
Generated
Vendored
+200
@@ -0,0 +1,200 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.longestCommonPrefix = longestCommonPrefix;
|
||||
exports.longestCommonSuffix = longestCommonSuffix;
|
||||
exports.replacePrefix = replacePrefix;
|
||||
exports.replaceSuffix = replaceSuffix;
|
||||
exports.removePrefix = removePrefix;
|
||||
exports.removeSuffix = removeSuffix;
|
||||
exports.maximumOverlap = maximumOverlap;
|
||||
exports.hasOnlyWinLineEndings = hasOnlyWinLineEndings;
|
||||
exports.hasOnlyUnixLineEndings = hasOnlyUnixLineEndings;
|
||||
exports.segment = segment;
|
||||
exports.trailingWs = trailingWs;
|
||||
exports.leadingWs = leadingWs;
|
||||
exports.leadingAndTrailingWs = leadingAndTrailingWs;
|
||||
function longestCommonPrefix(str1, str2) {
|
||||
var i;
|
||||
for (i = 0; i < str1.length && i < str2.length; i++) {
|
||||
if (str1[i] != str2[i]) {
|
||||
return str1.slice(0, i);
|
||||
}
|
||||
}
|
||||
return str1.slice(0, i);
|
||||
}
|
||||
function longestCommonSuffix(str1, str2) {
|
||||
var i;
|
||||
// Unlike longestCommonPrefix, we need a special case to handle all scenarios
|
||||
// where we return the empty string since str1.slice(-0) will return the
|
||||
// entire string.
|
||||
if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) {
|
||||
return '';
|
||||
}
|
||||
for (i = 0; i < str1.length && i < str2.length; i++) {
|
||||
if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) {
|
||||
return str1.slice(-i);
|
||||
}
|
||||
}
|
||||
return str1.slice(-i);
|
||||
}
|
||||
function replacePrefix(string, oldPrefix, newPrefix) {
|
||||
if (string.slice(0, oldPrefix.length) != oldPrefix) {
|
||||
throw Error("string ".concat(JSON.stringify(string), " doesn't start with prefix ").concat(JSON.stringify(oldPrefix), "; this is a bug"));
|
||||
}
|
||||
return newPrefix + string.slice(oldPrefix.length);
|
||||
}
|
||||
function replaceSuffix(string, oldSuffix, newSuffix) {
|
||||
if (!oldSuffix) {
|
||||
return string + newSuffix;
|
||||
}
|
||||
if (string.slice(-oldSuffix.length) != oldSuffix) {
|
||||
throw Error("string ".concat(JSON.stringify(string), " doesn't end with suffix ").concat(JSON.stringify(oldSuffix), "; this is a bug"));
|
||||
}
|
||||
return string.slice(0, -oldSuffix.length) + newSuffix;
|
||||
}
|
||||
function removePrefix(string, oldPrefix) {
|
||||
return replacePrefix(string, oldPrefix, '');
|
||||
}
|
||||
function removeSuffix(string, oldSuffix) {
|
||||
return replaceSuffix(string, oldSuffix, '');
|
||||
}
|
||||
function maximumOverlap(string1, string2) {
|
||||
return string2.slice(0, overlapCount(string1, string2));
|
||||
}
|
||||
// Nicked from https://stackoverflow.com/a/60422853/1709587
|
||||
function overlapCount(a, b) {
|
||||
// Deal with cases where the strings differ in length
|
||||
var startA = 0;
|
||||
if (a.length > b.length) {
|
||||
startA = a.length - b.length;
|
||||
}
|
||||
var endB = b.length;
|
||||
if (a.length < b.length) {
|
||||
endB = a.length;
|
||||
}
|
||||
// Create a back-reference for each index
|
||||
// that should be followed in case of a mismatch.
|
||||
// We only need B to make these references:
|
||||
var map = Array(endB);
|
||||
var k = 0; // Index that lags behind j
|
||||
map[0] = 0;
|
||||
for (var j = 1; j < endB; j++) {
|
||||
if (b[j] == b[k]) {
|
||||
map[j] = map[k]; // skip over the same character (optional optimisation)
|
||||
}
|
||||
else {
|
||||
map[j] = k;
|
||||
}
|
||||
while (k > 0 && b[j] != b[k]) {
|
||||
k = map[k];
|
||||
}
|
||||
if (b[j] == b[k]) {
|
||||
k++;
|
||||
}
|
||||
}
|
||||
// Phase 2: use these references while iterating over A
|
||||
k = 0;
|
||||
for (var i = startA; i < a.length; i++) {
|
||||
while (k > 0 && a[i] != b[k]) {
|
||||
k = map[k];
|
||||
}
|
||||
if (a[i] == b[k]) {
|
||||
k++;
|
||||
}
|
||||
}
|
||||
return k;
|
||||
}
|
||||
/**
|
||||
* Returns true if the string consistently uses Windows line endings.
|
||||
*/
|
||||
function hasOnlyWinLineEndings(string) {
|
||||
return string.includes('\r\n') && !string.startsWith('\n') && !string.match(/[^\r]\n/);
|
||||
}
|
||||
/**
|
||||
* Returns true if the string consistently uses Unix line endings.
|
||||
*/
|
||||
function hasOnlyUnixLineEndings(string) {
|
||||
return !string.includes('\r\n') && string.includes('\n');
|
||||
}
|
||||
/**
|
||||
* Split a string into segments using a word segmenter, merging consecutive
|
||||
* segments if they are both whitespace segments. Whitespace segments can
|
||||
* appear adjacent to one another for two reasons:
|
||||
* - newlines always get their own segment
|
||||
* - where a diacritic is attached to a whitespace character in the text, the
|
||||
* segment ends after the diacritic, so e.g. " \u0300 " becomes two segments.
|
||||
* This function therefore runs the segmenter's .segment() method and then
|
||||
* merges consecutive segments of whitespace into a single part.
|
||||
*/
|
||||
function segment(string, segmenter) {
|
||||
var parts = [];
|
||||
for (var _i = 0, _a = Array.from(segmenter.segment(string)); _i < _a.length; _i++) {
|
||||
var segmentObj = _a[_i];
|
||||
var segment_1 = segmentObj.segment;
|
||||
if (parts.length && (/\s/).test(parts[parts.length - 1]) && (/\s/).test(segment_1)) {
|
||||
parts[parts.length - 1] += segment_1;
|
||||
}
|
||||
else {
|
||||
parts.push(segment_1);
|
||||
}
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
// The functions below take a `segmenter` argument so that, when called from
|
||||
// diffWords when it is using a segmenter, they can use a notion of what
|
||||
// constitutes "whitespace" that is consistent with the segmenter.
|
||||
//
|
||||
// USUALLY this will be identical to the result of the non-segmenter-based
|
||||
// logic, but it differs in at least one case: when whitespace characters are
|
||||
// modified by diacritics. A word segmenter considers these diacritics to be
|
||||
// part of the whitespace, whereas our non-segmenter-based logic does not.
|
||||
//
|
||||
// Because the segmenter-based approach necessarily requires segmenting the
|
||||
// entire string, we offer a leadingAndTrailingWs function to allow getting the
|
||||
// whitespace prefix AND whitespace suffix with a single call to the segmenter,
|
||||
// for efficiency's sake.
|
||||
function trailingWs(string, segmenter) {
|
||||
if (segmenter) {
|
||||
return leadingAndTrailingWs(string, segmenter)[1];
|
||||
}
|
||||
// Yes, this looks overcomplicated and dumb - why not replace the whole function with
|
||||
// return string.match(/\s*$/)[0]
|
||||
// you ask? Because:
|
||||
// 1. the trap described at https://markamery.com/blog/quadratic-time-regexes/ would mean doing
|
||||
// this would cause this function to take O(n²) time in the worst case (specifically when
|
||||
// there is a massive run of NON-TRAILING whitespace in `string`), and
|
||||
// 2. the fix proposed in the same blog post, of using a negative lookbehind, is incompatible
|
||||
// with old Safari versions that we'd like to not break if possible (see
|
||||
// https://github.com/kpdecker/jsdiff/pull/550)
|
||||
// It feels absurd to do this with an explicit loop instead of a regex, but I really can't see a
|
||||
// better way that doesn't result in broken behaviour.
|
||||
var i;
|
||||
for (i = string.length - 1; i >= 0; i--) {
|
||||
if (!string[i].match(/\s/)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return string.substring(i + 1);
|
||||
}
|
||||
function leadingWs(string, segmenter) {
|
||||
if (segmenter) {
|
||||
return leadingAndTrailingWs(string, segmenter)[0];
|
||||
}
|
||||
// Thankfully the annoying considerations described in trailingWs don't apply here:
|
||||
var match = string.match(/^\s*/);
|
||||
return match ? match[0] : '';
|
||||
}
|
||||
function leadingAndTrailingWs(string, segmenter) {
|
||||
if (!segmenter) {
|
||||
return [leadingWs(string), trailingWs(string)];
|
||||
}
|
||||
if (segmenter.resolvedOptions().granularity != 'word') {
|
||||
throw new Error('The segmenter passed must have a granularity of "word"');
|
||||
}
|
||||
var segments = segment(string, segmenter);
|
||||
var firstSeg = segments[0];
|
||||
var lastSeg = segments[segments.length - 1];
|
||||
var head = (/\s/).test(firstSeg) ? firstSeg : '';
|
||||
var tail = (/\s/).test(lastSeg) ? lastSeg : '';
|
||||
return [head, tail];
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* converts a list of change objects to the format returned by Google's [diff-match-patch](https://github.com/google/diff-match-patch) library
|
||||
*/
|
||||
export function convertChangesToDMP(changes) {
|
||||
const ret = [];
|
||||
let change, operation;
|
||||
for (let i = 0; i < changes.length; i++) {
|
||||
change = changes[i];
|
||||
if (change.added) {
|
||||
operation = 1;
|
||||
}
|
||||
else if (change.removed) {
|
||||
operation = -1;
|
||||
}
|
||||
else {
|
||||
operation = 0;
|
||||
}
|
||||
ret.push([operation, change.value]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* converts a list of change objects to a serialized XML format
|
||||
*/
|
||||
export function convertChangesToXML(changes) {
|
||||
const ret = [];
|
||||
for (let i = 0; i < changes.length; i++) {
|
||||
const change = changes[i];
|
||||
if (change.added) {
|
||||
ret.push('<ins>');
|
||||
}
|
||||
else if (change.removed) {
|
||||
ret.push('<del>');
|
||||
}
|
||||
ret.push(escapeHTML(change.value));
|
||||
if (change.added) {
|
||||
ret.push('</ins>');
|
||||
}
|
||||
else if (change.removed) {
|
||||
ret.push('</del>');
|
||||
}
|
||||
}
|
||||
return ret.join('');
|
||||
}
|
||||
function escapeHTML(s) {
|
||||
let n = s;
|
||||
n = n.replace(/&/g, '&');
|
||||
n = n.replace(/</g, '<');
|
||||
n = n.replace(/>/g, '>');
|
||||
n = n.replace(/"/g, '"');
|
||||
return n;
|
||||
}
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
import Diff from './base.js';
|
||||
class ArrayDiff extends Diff {
|
||||
tokenize(value) {
|
||||
return value.slice();
|
||||
}
|
||||
join(value) {
|
||||
return value;
|
||||
}
|
||||
removeEmpty(value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
export const arrayDiff = new ArrayDiff();
|
||||
export function diffArrays(oldArr, newArr, options) {
|
||||
return arrayDiff.diff(oldArr, newArr, options);
|
||||
}
|
||||
Generated
Vendored
+253
@@ -0,0 +1,253 @@
|
||||
export default class Diff {
|
||||
diff(oldStr, newStr,
|
||||
// Type below is not accurate/complete - see above for full possibilities - but it compiles
|
||||
options = {}) {
|
||||
let callback;
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
else if ('callback' in options) {
|
||||
callback = options.callback;
|
||||
}
|
||||
// Allow subclasses to massage the input prior to running
|
||||
const oldString = this.castInput(oldStr, options);
|
||||
const newString = this.castInput(newStr, options);
|
||||
const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
|
||||
const newTokens = this.removeEmpty(this.tokenize(newString, options));
|
||||
return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
|
||||
}
|
||||
diffWithOptionsObj(oldTokens, newTokens, options, callback) {
|
||||
var _a;
|
||||
const done = (value) => {
|
||||
value = this.postProcess(value, options);
|
||||
if (callback) {
|
||||
setTimeout(function () { callback(value); }, 0);
|
||||
return undefined;
|
||||
}
|
||||
else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
const newLen = newTokens.length, oldLen = oldTokens.length;
|
||||
let editLength = 1;
|
||||
let maxEditLength = newLen + oldLen;
|
||||
if (options.maxEditLength != null) {
|
||||
maxEditLength = Math.min(maxEditLength, options.maxEditLength);
|
||||
}
|
||||
const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
|
||||
const abortAfterTimestamp = Date.now() + maxExecutionTime;
|
||||
const bestPath = [{ oldPos: -1, lastComponent: undefined }];
|
||||
// Seed editLength = 0, i.e. the content starts with the same values
|
||||
let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
|
||||
if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
|
||||
// Identity per the equality and tokenizer
|
||||
return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
|
||||
}
|
||||
// Once we hit the right edge of the edit graph on some diagonal k, we can
|
||||
// definitely reach the end of the edit graph in no more than k edits, so
|
||||
// there's no point in considering any moves to diagonal k+1 any more (from
|
||||
// which we're guaranteed to need at least k+1 more edits).
|
||||
// Similarly, once we've reached the bottom of the edit graph, there's no
|
||||
// point considering moves to lower diagonals.
|
||||
// We record this fact by setting minDiagonalToConsider and
|
||||
// maxDiagonalToConsider to some finite value once we've hit the edge of
|
||||
// the edit graph.
|
||||
// This optimization is not faithful to the original algorithm presented in
|
||||
// Myers's paper, which instead pointlessly extends D-paths off the end of
|
||||
// the edit graph - see page 7 of Myers's paper which notes this point
|
||||
// explicitly and illustrates it with a diagram. This has major performance
|
||||
// implications for some common scenarios. For instance, to compute a diff
|
||||
// where the new text simply appends d characters on the end of the
|
||||
// original text of length n, the true Myers algorithm will take O(n+d^2)
|
||||
// time while this optimization needs only O(n+d) time.
|
||||
let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
|
||||
// Main worker method. checks all permutations of a given edit length for acceptance.
|
||||
const execEditLength = () => {
|
||||
for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
|
||||
let basePath;
|
||||
const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
|
||||
if (removePath) {
|
||||
// No one else is going to attempt to use this value, clear it
|
||||
// @ts-expect-error - perf optimisation. This type-violating value will never be read.
|
||||
bestPath[diagonalPath - 1] = undefined;
|
||||
}
|
||||
let canAdd = false;
|
||||
if (addPath) {
|
||||
// what newPos will be after we do an insertion:
|
||||
const addPathNewPos = addPath.oldPos - diagonalPath;
|
||||
canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
|
||||
}
|
||||
const canRemove = removePath && removePath.oldPos + 1 < oldLen;
|
||||
if (!canAdd && !canRemove) {
|
||||
// If this path is a terminal then prune
|
||||
// @ts-expect-error - perf optimisation. This type-violating value will never be read.
|
||||
bestPath[diagonalPath] = undefined;
|
||||
continue;
|
||||
}
|
||||
// Select the diagonal that we want to branch from. We select the prior
|
||||
// path whose position in the old string is the farthest from the origin
|
||||
// and does not pass the bounds of the diff graph
|
||||
if (!canRemove || (canAdd && removePath.oldPos < addPath.oldPos)) {
|
||||
basePath = this.addToPath(addPath, true, false, 0, options);
|
||||
}
|
||||
else {
|
||||
basePath = this.addToPath(removePath, false, true, 1, options);
|
||||
}
|
||||
newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
|
||||
if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
|
||||
// If we have hit the end of both strings, then we are done
|
||||
return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
|
||||
}
|
||||
else {
|
||||
bestPath[diagonalPath] = basePath;
|
||||
if (basePath.oldPos + 1 >= oldLen) {
|
||||
maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
|
||||
}
|
||||
if (newPos + 1 >= newLen) {
|
||||
minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
editLength++;
|
||||
};
|
||||
// Performs the length of edit iteration. Is a bit fugly as this has to support the
|
||||
// sync and async mode which is never fun. Loops over execEditLength until a value
|
||||
// is produced, or until the edit length exceeds options.maxEditLength (if given),
|
||||
// in which case it will return undefined.
|
||||
if (callback) {
|
||||
(function exec() {
|
||||
setTimeout(function () {
|
||||
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
|
||||
return callback(undefined);
|
||||
}
|
||||
if (!execEditLength()) {
|
||||
exec();
|
||||
}
|
||||
}, 0);
|
||||
}());
|
||||
}
|
||||
else {
|
||||
while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
|
||||
const ret = execEditLength();
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addToPath(path, added, removed, oldPosInc, options) {
|
||||
const last = path.lastComponent;
|
||||
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
||||
return {
|
||||
oldPos: path.oldPos + oldPosInc,
|
||||
lastComponent: { count: last.count + 1, added: added, removed: removed, previousComponent: last.previousComponent }
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
oldPos: path.oldPos + oldPosInc,
|
||||
lastComponent: { count: 1, added: added, removed: removed, previousComponent: last }
|
||||
};
|
||||
}
|
||||
}
|
||||
extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
|
||||
const newLen = newTokens.length, oldLen = oldTokens.length;
|
||||
let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
|
||||
while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
|
||||
newPos++;
|
||||
oldPos++;
|
||||
commonCount++;
|
||||
if (options.oneChangePerToken) {
|
||||
basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
|
||||
}
|
||||
}
|
||||
if (commonCount && !options.oneChangePerToken) {
|
||||
basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
|
||||
}
|
||||
basePath.oldPos = oldPos;
|
||||
return newPos;
|
||||
}
|
||||
equals(left, right, options) {
|
||||
if (options.comparator) {
|
||||
return options.comparator(left, right);
|
||||
}
|
||||
else {
|
||||
return left === right
|
||||
|| (!!options.ignoreCase && left.toLowerCase() === right.toLowerCase());
|
||||
}
|
||||
}
|
||||
removeEmpty(array) {
|
||||
const ret = [];
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (array[i]) {
|
||||
ret.push(array[i]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
castInput(value, options) {
|
||||
return value;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
tokenize(value, options) {
|
||||
return Array.from(value);
|
||||
}
|
||||
join(chars) {
|
||||
// Assumes ValueT is string, which is the case for most subclasses.
|
||||
// When it's false, e.g. in diffArrays, this method needs to be overridden (e.g. with a no-op)
|
||||
// Yes, the casts are verbose and ugly, because this pattern - of having the base class SORT OF
|
||||
// assume tokens and values are strings, but not completely - is weird and janky.
|
||||
return chars.join('');
|
||||
}
|
||||
postProcess(changeObjects,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
options) {
|
||||
return changeObjects;
|
||||
}
|
||||
get useLongestToken() {
|
||||
return false;
|
||||
}
|
||||
buildValues(lastComponent, newTokens, oldTokens) {
|
||||
// First we convert our linked list of components in reverse order to an
|
||||
// array in the right order:
|
||||
const components = [];
|
||||
let nextComponent;
|
||||
while (lastComponent) {
|
||||
components.push(lastComponent);
|
||||
nextComponent = lastComponent.previousComponent;
|
||||
delete lastComponent.previousComponent;
|
||||
lastComponent = nextComponent;
|
||||
}
|
||||
components.reverse();
|
||||
const componentLen = components.length;
|
||||
let componentPos = 0, newPos = 0, oldPos = 0;
|
||||
for (; componentPos < componentLen; componentPos++) {
|
||||
const component = components[componentPos];
|
||||
if (!component.removed) {
|
||||
if (!component.added && this.useLongestToken) {
|
||||
let value = newTokens.slice(newPos, newPos + component.count);
|
||||
value = value.map(function (value, i) {
|
||||
const oldValue = oldTokens[oldPos + i];
|
||||
return oldValue.length > value.length ? oldValue : value;
|
||||
});
|
||||
component.value = this.join(value);
|
||||
}
|
||||
else {
|
||||
component.value = this.join(newTokens.slice(newPos, newPos + component.count));
|
||||
}
|
||||
newPos += component.count;
|
||||
// Common case
|
||||
if (!component.added) {
|
||||
oldPos += component.count;
|
||||
}
|
||||
}
|
||||
else {
|
||||
component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
|
||||
oldPos += component.count;
|
||||
}
|
||||
}
|
||||
return components;
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
import Diff from './base.js';
|
||||
class CharacterDiff extends Diff {
|
||||
}
|
||||
export const characterDiff = new CharacterDiff();
|
||||
export function diffChars(oldStr, newStr, options) {
|
||||
return characterDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
Generated
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
import Diff from './base.js';
|
||||
class CssDiff extends Diff {
|
||||
tokenize(value) {
|
||||
return value.split(/([{}:;,]|\s+)/);
|
||||
}
|
||||
}
|
||||
export const cssDiff = new CssDiff();
|
||||
export function diffCss(oldStr, newStr, options) {
|
||||
return cssDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
Generated
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
import Diff from './base.js';
|
||||
import { tokenize } from './line.js';
|
||||
class JsonDiff extends Diff {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.tokenize = tokenize;
|
||||
}
|
||||
get useLongestToken() {
|
||||
// Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
|
||||
// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
|
||||
return true;
|
||||
}
|
||||
castInput(value, options) {
|
||||
const { undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v } = options;
|
||||
return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), null, ' ');
|
||||
}
|
||||
equals(left, right, options) {
|
||||
return super.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options);
|
||||
}
|
||||
}
|
||||
export const jsonDiff = new JsonDiff();
|
||||
export function diffJson(oldStr, newStr, options) {
|
||||
return jsonDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
// This function handles the presence of circular references by bailing out when encountering an
|
||||
// object that is already on the "stack" of items being processed. Accepts an optional replacer
|
||||
export function canonicalize(obj, stack, replacementStack, replacer, key) {
|
||||
stack = stack || [];
|
||||
replacementStack = replacementStack || [];
|
||||
if (replacer) {
|
||||
obj = replacer(key === undefined ? '' : key, obj);
|
||||
}
|
||||
let i;
|
||||
for (i = 0; i < stack.length; i += 1) {
|
||||
if (stack[i] === obj) {
|
||||
return replacementStack[i];
|
||||
}
|
||||
}
|
||||
let canonicalizedObj;
|
||||
if ('[object Array]' === Object.prototype.toString.call(obj)) {
|
||||
stack.push(obj);
|
||||
canonicalizedObj = new Array(obj.length);
|
||||
replacementStack.push(canonicalizedObj);
|
||||
for (i = 0; i < obj.length; i += 1) {
|
||||
canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, String(i));
|
||||
}
|
||||
stack.pop();
|
||||
replacementStack.pop();
|
||||
return canonicalizedObj;
|
||||
}
|
||||
if (obj && obj.toJSON) {
|
||||
obj = obj.toJSON();
|
||||
}
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
stack.push(obj);
|
||||
canonicalizedObj = {};
|
||||
replacementStack.push(canonicalizedObj);
|
||||
const sortedKeys = [];
|
||||
let key;
|
||||
for (key in obj) {
|
||||
/* istanbul ignore else */
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
sortedKeys.push(key);
|
||||
}
|
||||
}
|
||||
sortedKeys.sort();
|
||||
for (i = 0; i < sortedKeys.length; i += 1) {
|
||||
key = sortedKeys[i];
|
||||
canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key);
|
||||
}
|
||||
stack.pop();
|
||||
replacementStack.pop();
|
||||
}
|
||||
else {
|
||||
canonicalizedObj = obj;
|
||||
}
|
||||
return canonicalizedObj;
|
||||
}
|
||||
Generated
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
import Diff from './base.js';
|
||||
import { generateOptions } from '../util/params.js';
|
||||
class LineDiff extends Diff {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.tokenize = tokenize;
|
||||
}
|
||||
equals(left, right, options) {
|
||||
// If we're ignoring whitespace, we need to normalise lines by stripping
|
||||
// whitespace before checking equality. (This has an annoying interaction
|
||||
// with newlineIsToken that requires special handling: if newlines get their
|
||||
// own token, then we DON'T want to trim the *newline* tokens down to empty
|
||||
// strings, since this would cause us to treat whitespace-only line content
|
||||
// as equal to a separator between lines, which would be weird and
|
||||
// inconsistent with the documented behavior of the options.)
|
||||
if (options.ignoreWhitespace) {
|
||||
if (!options.newlineIsToken || !left.includes('\n')) {
|
||||
left = left.trim();
|
||||
}
|
||||
if (!options.newlineIsToken || !right.includes('\n')) {
|
||||
right = right.trim();
|
||||
}
|
||||
}
|
||||
else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
|
||||
if (left.endsWith('\n')) {
|
||||
left = left.slice(0, -1);
|
||||
}
|
||||
if (right.endsWith('\n')) {
|
||||
right = right.slice(0, -1);
|
||||
}
|
||||
}
|
||||
return super.equals(left, right, options);
|
||||
}
|
||||
}
|
||||
export const lineDiff = new LineDiff();
|
||||
export function diffLines(oldStr, newStr, options) {
|
||||
return lineDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
export function diffTrimmedLines(oldStr, newStr, options) {
|
||||
options = generateOptions(options, { ignoreWhitespace: true });
|
||||
return lineDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
// Exported standalone so it can be used from jsonDiff too.
|
||||
export function tokenize(value, options) {
|
||||
if (options.stripTrailingCr) {
|
||||
// remove one \r before \n to match GNU diff's --strip-trailing-cr behavior
|
||||
value = value.replace(/\r\n/g, '\n');
|
||||
}
|
||||
const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
|
||||
// Ignore the final empty token that occurs if the string ends with a new line
|
||||
if (!linesAndNewlines[linesAndNewlines.length - 1]) {
|
||||
linesAndNewlines.pop();
|
||||
}
|
||||
// Merge the content and line separators into single tokens
|
||||
for (let i = 0; i < linesAndNewlines.length; i++) {
|
||||
const line = linesAndNewlines[i];
|
||||
if (i % 2 && !options.newlineIsToken) {
|
||||
retLines[retLines.length - 1] += line;
|
||||
}
|
||||
else {
|
||||
retLines.push(line);
|
||||
}
|
||||
}
|
||||
return retLines;
|
||||
}
|
||||
Generated
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
import Diff from './base.js';
|
||||
function isSentenceEndPunct(char) {
|
||||
return char == '.' || char == '!' || char == '?';
|
||||
}
|
||||
class SentenceDiff extends Diff {
|
||||
tokenize(value) {
|
||||
var _a;
|
||||
// If in future we drop support for environments that don't support lookbehinds, we can replace
|
||||
// this entire function with:
|
||||
// return value.split(/(?<=[.!?])(\s+|$)/);
|
||||
// but until then, for similar reasons to the trailingWs function in string.ts, we are forced
|
||||
// to do this verbosely "by hand" instead of using a regex.
|
||||
const result = [];
|
||||
let tokenStartI = 0;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (i == value.length - 1) {
|
||||
result.push(value.slice(tokenStartI));
|
||||
break;
|
||||
}
|
||||
if (isSentenceEndPunct(value[i]) && value[i + 1].match(/\s/)) {
|
||||
// We've hit a sentence break - i.e. a punctuation mark followed by whitespace.
|
||||
// We now want to push TWO tokens to the result:
|
||||
// 1. the sentence
|
||||
result.push(value.slice(tokenStartI, i + 1));
|
||||
// 2. the whitespace
|
||||
i = tokenStartI = i + 1;
|
||||
while ((_a = value[i + 1]) === null || _a === void 0 ? void 0 : _a.match(/\s/)) {
|
||||
i++;
|
||||
}
|
||||
result.push(value.slice(tokenStartI, i + 1));
|
||||
// Then the next token (a sentence) starts on the character after the whitespace.
|
||||
// (It's okay if this is off the end of the string - then the outer loop will terminate
|
||||
// here anyway.)
|
||||
tokenStartI = i + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
export const sentenceDiff = new SentenceDiff();
|
||||
export function diffSentences(oldStr, newStr, options) {
|
||||
return sentenceDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
Generated
Vendored
+281
@@ -0,0 +1,281 @@
|
||||
import Diff from './base.js';
|
||||
import { longestCommonPrefix, longestCommonSuffix, replacePrefix, replaceSuffix, removePrefix, removeSuffix, maximumOverlap, leadingWs, trailingWs, leadingAndTrailingWs, segment } from '../util/string.js';
|
||||
// Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode
|
||||
//
|
||||
// Chars/ranges counted as "word" characters by this regex are as follows:
|
||||
//
|
||||
// + U+00AD Soft hyphen
|
||||
// + 00C0–00FF (letters with diacritics from the Latin-1 Supplement), except:
|
||||
// - U+00D7 × Multiplication sign
|
||||
// - U+00F7 ÷ Division sign
|
||||
// + Latin Extended-A, 0100–017F
|
||||
// + Latin Extended-B, 0180–024F
|
||||
// + IPA Extensions, 0250–02AF
|
||||
// + Spacing Modifier Letters, 02B0–02FF, except:
|
||||
// - U+02C7 ˇ ˇ Caron
|
||||
// - U+02D8 ˘ ˘ Breve
|
||||
// - U+02D9 ˙ ˙ Dot Above
|
||||
// - U+02DA ˚ ˚ Ring Above
|
||||
// - U+02DB ˛ ˛ Ogonek
|
||||
// - U+02DC ˜ ˜ Small Tilde
|
||||
// - U+02DD ˝ ˝ Double Acute Accent
|
||||
// + Latin Extended Additional, 1E00–1EFF
|
||||
const extendedWordChars = 'a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}';
|
||||
// Each token is one of the following:
|
||||
// - A punctuation mark plus the surrounding whitespace
|
||||
// - A word plus the surrounding whitespace
|
||||
// - Pure whitespace (but only in the special case where the entire text
|
||||
// is just whitespace)
|
||||
//
|
||||
// We have to include surrounding whitespace in the tokens because the two
|
||||
// alternative approaches produce horribly broken results:
|
||||
// * If we just discard the whitespace, we can't fully reproduce the original
|
||||
// text from the sequence of tokens and any attempt to render the diff will
|
||||
// get the whitespace wrong.
|
||||
// * If we have separate tokens for whitespace, then in a typical text every
|
||||
// second token will be a single space character. But this often results in
|
||||
// the optimal diff between two texts being a perverse one that preserves
|
||||
// the spaces between words but deletes and reinserts actual common words.
|
||||
// See https://github.com/kpdecker/jsdiff/issues/160#issuecomment-1866099640
|
||||
// for an example.
|
||||
//
|
||||
// Keeping the surrounding whitespace of course has implications for .equals
|
||||
// and .join, not just .tokenize.
|
||||
// This regex does NOT fully implement the tokenization rules described above.
|
||||
// Instead, it gives runs of whitespace their own "token". The tokenize method
|
||||
// then handles stitching whitespace tokens onto adjacent word or punctuation
|
||||
// tokens.
|
||||
const tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, 'ug');
|
||||
class WordDiff extends Diff {
|
||||
equals(left, right, options) {
|
||||
if (options.ignoreCase) {
|
||||
left = left.toLowerCase();
|
||||
right = right.toLowerCase();
|
||||
}
|
||||
return left.trim() === right.trim();
|
||||
}
|
||||
tokenize(value, options = {}) {
|
||||
let parts;
|
||||
if (options.intlSegmenter) {
|
||||
const segmenter = options.intlSegmenter;
|
||||
if (segmenter.resolvedOptions().granularity != 'word') {
|
||||
throw new Error('The segmenter passed must have a granularity of "word"');
|
||||
}
|
||||
// We want `parts` to be an array whose elements alternate between being
|
||||
// pure whitespace and being pure non-whitespace. This is ALMOST what the
|
||||
// segments returned by a word-based Intl.Segmenter already look like,
|
||||
// but not quite - see explanation in the docs of our custom segment()
|
||||
// function.
|
||||
parts = segment(value, segmenter);
|
||||
}
|
||||
else {
|
||||
parts = value.match(tokenizeIncludingWhitespace) || [];
|
||||
}
|
||||
const tokens = [];
|
||||
let prevPart = null;
|
||||
parts.forEach(part => {
|
||||
if ((/\s/).test(part)) {
|
||||
if (prevPart == null) {
|
||||
tokens.push(part);
|
||||
}
|
||||
else {
|
||||
tokens.push(tokens.pop() + part);
|
||||
}
|
||||
}
|
||||
else if (prevPart != null && (/\s/).test(prevPart)) {
|
||||
if (tokens[tokens.length - 1] == prevPart) {
|
||||
tokens.push(tokens.pop() + part);
|
||||
}
|
||||
else {
|
||||
tokens.push(prevPart + part);
|
||||
}
|
||||
}
|
||||
else {
|
||||
tokens.push(part);
|
||||
}
|
||||
prevPart = part;
|
||||
});
|
||||
return tokens;
|
||||
}
|
||||
join(tokens) {
|
||||
// Tokens being joined here will always have appeared consecutively in the
|
||||
// same text, so we can simply strip off the leading whitespace from all the
|
||||
// tokens except the first (and except any whitespace-only tokens - but such
|
||||
// a token will always be the first and only token anyway) and then join them
|
||||
// and the whitespace around words and punctuation will end up correct.
|
||||
return tokens.map((token, i) => {
|
||||
if (i == 0) {
|
||||
return token;
|
||||
}
|
||||
else {
|
||||
return token.replace((/^\s+/), '');
|
||||
}
|
||||
}).join('');
|
||||
}
|
||||
postProcess(changes, options) {
|
||||
if (!changes || options.oneChangePerToken) {
|
||||
return changes;
|
||||
}
|
||||
let lastKeep = null;
|
||||
// Change objects representing any insertion or deletion since the last
|
||||
// "keep" change object. There can be at most one of each.
|
||||
let insertion = null;
|
||||
let deletion = null;
|
||||
changes.forEach(change => {
|
||||
if (change.added) {
|
||||
insertion = change;
|
||||
}
|
||||
else if (change.removed) {
|
||||
deletion = change;
|
||||
}
|
||||
else {
|
||||
if (insertion || deletion) { // May be false at start of text
|
||||
dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change, options.intlSegmenter);
|
||||
}
|
||||
lastKeep = change;
|
||||
insertion = null;
|
||||
deletion = null;
|
||||
}
|
||||
});
|
||||
if (insertion || deletion) {
|
||||
dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null, options.intlSegmenter);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
export const wordDiff = new WordDiff();
|
||||
export function diffWords(oldStr, newStr, options) {
|
||||
// This option has never been documented and never will be (it's clearer to
|
||||
// just call `diffWordsWithSpace` directly if you need that behavior), but
|
||||
// has existed in jsdiff for a long time, so we retain support for it here
|
||||
// for the sake of backwards compatibility.
|
||||
if ((options === null || options === void 0 ? void 0 : options.ignoreWhitespace) != null && !options.ignoreWhitespace) {
|
||||
return diffWordsWithSpace(oldStr, newStr, options);
|
||||
}
|
||||
return wordDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep, segmenter) {
|
||||
// Before returning, we tidy up the leading and trailing whitespace of the
|
||||
// change objects to eliminate cases where trailing whitespace in one object
|
||||
// is repeated as leading whitespace in the next.
|
||||
// Below are examples of the outcomes we want here to explain the code.
|
||||
// I=insert, K=keep, D=delete
|
||||
// 1. diffing 'foo bar baz' vs 'foo baz'
|
||||
// Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
|
||||
// After cleanup, we want: K:'foo ' D:'bar ' K:'baz'
|
||||
//
|
||||
// 2. Diffing 'foo bar baz' vs 'foo qux baz'
|
||||
// Prior to cleanup, we have K:'foo ' D:' bar ' I:' qux ' K:' baz'
|
||||
// After cleanup, we want K:'foo ' D:'bar' I:'qux' K:' baz'
|
||||
//
|
||||
// 3. Diffing 'foo\nbar baz' vs 'foo baz'
|
||||
// Prior to cleanup, we have K:'foo ' D:'\nbar ' K:' baz'
|
||||
// After cleanup, we want K'foo' D:'\nbar' K:' baz'
|
||||
//
|
||||
// 4. Diffing 'foo baz' vs 'foo\nbar baz'
|
||||
// Prior to cleanup, we have K:'foo\n' I:'\nbar ' K:' baz'
|
||||
// After cleanup, we ideally want K'foo' I:'\nbar' K:' baz'
|
||||
// but don't actually manage this currently (the pre-cleanup change
|
||||
// objects don't contain enough information to make it possible).
|
||||
//
|
||||
// 5. Diffing 'foo bar baz' vs 'foo baz'
|
||||
// Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
|
||||
// After cleanup, we want K:'foo ' D:' bar ' K:'baz'
|
||||
//
|
||||
// Our handling is unavoidably imperfect in the case where there's a single
|
||||
// indel between keeps and the whitespace has changed. For instance, consider
|
||||
// diffing 'foo\tbar\nbaz' vs 'foo baz'. Unless we create an extra change
|
||||
// object to represent the insertion of the space character (which isn't even
|
||||
// a token), we have no way to avoid losing information about the texts'
|
||||
// original whitespace in the result we return. Still, we do our best to
|
||||
// output something that will look sensible if we e.g. print it with
|
||||
// insertions in green and deletions in red.
|
||||
// Between two "keep" change objects (or before the first or after the last
|
||||
// change object), we can have either:
|
||||
// * A "delete" followed by an "insert"
|
||||
// * Just an "insert"
|
||||
// * Just a "delete"
|
||||
// We handle the three cases separately.
|
||||
if (deletion && insertion) {
|
||||
const [oldWsPrefix, oldWsSuffix] = leadingAndTrailingWs(deletion.value, segmenter);
|
||||
const [newWsPrefix, newWsSuffix] = leadingAndTrailingWs(insertion.value, segmenter);
|
||||
if (startKeep) {
|
||||
const commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
|
||||
startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
|
||||
deletion.value = removePrefix(deletion.value, commonWsPrefix);
|
||||
insertion.value = removePrefix(insertion.value, commonWsPrefix);
|
||||
}
|
||||
if (endKeep) {
|
||||
const commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix);
|
||||
endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix);
|
||||
deletion.value = removeSuffix(deletion.value, commonWsSuffix);
|
||||
insertion.value = removeSuffix(insertion.value, commonWsSuffix);
|
||||
}
|
||||
}
|
||||
else if (insertion) {
|
||||
// The whitespaces all reflect what was in the new text rather than
|
||||
// the old, so we essentially have no information about whitespace
|
||||
// insertion or deletion. We just want to dedupe the whitespace.
|
||||
// We do that by having each change object keep its trailing
|
||||
// whitespace and deleting duplicate leading whitespace where
|
||||
// present.
|
||||
if (startKeep) {
|
||||
const ws = leadingWs(insertion.value, segmenter);
|
||||
insertion.value = insertion.value.substring(ws.length);
|
||||
}
|
||||
if (endKeep) {
|
||||
const ws = leadingWs(endKeep.value, segmenter);
|
||||
endKeep.value = endKeep.value.substring(ws.length);
|
||||
}
|
||||
// otherwise we've got a deletion and no insertion
|
||||
}
|
||||
else if (startKeep && endKeep) {
|
||||
const newWsFull = leadingWs(endKeep.value, segmenter), [delWsStart, delWsEnd] = leadingAndTrailingWs(deletion.value, segmenter);
|
||||
// Any whitespace that comes straight after startKeep in both the old and
|
||||
// new texts, assign to startKeep and remove from the deletion.
|
||||
const newWsStart = longestCommonPrefix(newWsFull, delWsStart);
|
||||
deletion.value = removePrefix(deletion.value, newWsStart);
|
||||
// Any whitespace that comes straight before endKeep in both the old and
|
||||
// new texts, and hasn't already been assigned to startKeep, assign to
|
||||
// endKeep and remove from the deletion.
|
||||
const newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
|
||||
deletion.value = removeSuffix(deletion.value, newWsEnd);
|
||||
endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
|
||||
// If there's any whitespace from the new text that HASN'T already been
|
||||
// assigned, assign it to the start:
|
||||
startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
|
||||
}
|
||||
else if (endKeep) {
|
||||
// We are at the start of the text. Preserve all the whitespace on
|
||||
// endKeep, and just remove whitespace from the end of deletion to the
|
||||
// extent that it overlaps with the start of endKeep.
|
||||
const endKeepWsPrefix = leadingWs(endKeep.value, segmenter);
|
||||
const deletionWsSuffix = trailingWs(deletion.value, segmenter);
|
||||
const overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix);
|
||||
deletion.value = removeSuffix(deletion.value, overlap);
|
||||
}
|
||||
else if (startKeep) {
|
||||
// We are at the END of the text. Preserve all the whitespace on
|
||||
// startKeep, and just remove whitespace from the start of deletion to
|
||||
// the extent that it overlaps with the end of startKeep.
|
||||
const startKeepWsSuffix = trailingWs(startKeep.value, segmenter);
|
||||
const deletionWsPrefix = leadingWs(deletion.value, segmenter);
|
||||
const overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix);
|
||||
deletion.value = removePrefix(deletion.value, overlap);
|
||||
}
|
||||
}
|
||||
class WordsWithSpaceDiff extends Diff {
|
||||
tokenize(value) {
|
||||
// Slightly different to the tokenizeIncludingWhitespace regex used above in
|
||||
// that this one treats each individual newline as a distinct token, rather
|
||||
// than merging them into other surrounding whitespace. This was requested
|
||||
// in https://github.com/kpdecker/jsdiff/issues/180 &
|
||||
// https://github.com/kpdecker/jsdiff/issues/211
|
||||
const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, 'ug');
|
||||
return value.match(regex) || [];
|
||||
}
|
||||
}
|
||||
export const wordsWithSpaceDiff = new WordsWithSpaceDiff();
|
||||
export function diffWordsWithSpace(oldStr, newStr, options) {
|
||||
return wordsWithSpaceDiff.diff(oldStr, newStr, options);
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/* See LICENSE file for terms of use */
|
||||
/*
|
||||
* Text diff implementation.
|
||||
*
|
||||
* This library supports the following APIs:
|
||||
* Diff.diffChars: Character by character diff
|
||||
* Diff.diffWords: Word (as defined by \b regex) diff which ignores whitespace
|
||||
* Diff.diffLines: Line based diff
|
||||
*
|
||||
* Diff.diffCss: Diff targeted at CSS content
|
||||
*
|
||||
* These methods are based on the implementation proposed in
|
||||
* "An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
|
||||
* http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
|
||||
*/
|
||||
import Diff from './diff/base.js';
|
||||
import { diffChars, characterDiff } from './diff/character.js';
|
||||
import { diffWords, diffWordsWithSpace, wordDiff, wordsWithSpaceDiff } from './diff/word.js';
|
||||
import { diffLines, diffTrimmedLines, lineDiff } from './diff/line.js';
|
||||
import { diffSentences, sentenceDiff } from './diff/sentence.js';
|
||||
import { diffCss, cssDiff } from './diff/css.js';
|
||||
import { diffJson, canonicalize, jsonDiff } from './diff/json.js';
|
||||
import { diffArrays, arrayDiff } from './diff/array.js';
|
||||
import { applyPatch, applyPatches } from './patch/apply.js';
|
||||
import { parsePatch } from './patch/parse.js';
|
||||
import { reversePatch } from './patch/reverse.js';
|
||||
import { structuredPatch, createTwoFilesPatch, createPatch, formatPatch, INCLUDE_HEADERS, FILE_HEADERS_ONLY, OMIT_HEADERS } from './patch/create.js';
|
||||
import { convertChangesToDMP } from './convert/dmp.js';
|
||||
import { convertChangesToXML } from './convert/xml.js';
|
||||
export { Diff, diffChars, characterDiff, diffWords, wordDiff, diffWordsWithSpace, wordsWithSpaceDiff, diffLines, lineDiff, diffTrimmedLines, diffSentences, sentenceDiff, diffCss, cssDiff, diffJson, jsonDiff, diffArrays, arrayDiff, structuredPatch, createTwoFilesPatch, createPatch, formatPatch, INCLUDE_HEADERS, FILE_HEADERS_ONLY, OMIT_HEADERS, applyPatch, applyPatches, parsePatch, reversePatch, convertChangesToDMP, convertChangesToXML, canonicalize };
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"type":"module","sideEffects":false}
|
||||
Generated
Vendored
+257
@@ -0,0 +1,257 @@
|
||||
import { hasOnlyWinLineEndings, hasOnlyUnixLineEndings } from '../util/string.js';
|
||||
import { isWin, isUnix, unixToWin, winToUnix } from './line-endings.js';
|
||||
import { parsePatch } from './parse.js';
|
||||
import distanceIterator from '../util/distance-iterator.js';
|
||||
/**
|
||||
* attempts to apply a unified diff patch.
|
||||
*
|
||||
* Hunks are applied first to last.
|
||||
* `applyPatch` first tries to apply the first hunk at the line number specified in the hunk header, and with all context lines matching exactly.
|
||||
* If that fails, it tries scanning backwards and forwards, one line at a time, to find a place to apply the hunk where the context lines match exactly.
|
||||
* If that still fails, and `fuzzFactor` is greater than zero, it increments the maximum number of mismatches (missing, extra, or changed context lines) that there can be between the hunk context and a region where we are trying to apply the patch such that the hunk will still be considered to match.
|
||||
* Regardless of `fuzzFactor`, lines to be deleted in the hunk *must* be present for a hunk to match, and the context lines *immediately* before and after an insertion must match exactly.
|
||||
*
|
||||
* Once a hunk is successfully fitted, the process begins again with the next hunk.
|
||||
* Regardless of `fuzzFactor`, later hunks must be applied later in the file than earlier hunks.
|
||||
*
|
||||
* If a hunk cannot be successfully fitted *anywhere* with fewer than `fuzzFactor` mismatches, `applyPatch` fails and returns `false`.
|
||||
*
|
||||
* If a hunk is successfully fitted but not at the line number specified by the hunk header, all subsequent hunks have their target line number adjusted accordingly.
|
||||
* (e.g. if the first hunk is applied 10 lines below where the hunk header said it should fit, `applyPatch` will *start* looking for somewhere to apply the second hunk 10 lines below where its hunk header says it goes.)
|
||||
*
|
||||
* If the patch was applied successfully, returns a string containing the patched text.
|
||||
* If the patch could not be applied (because some hunks in the patch couldn't be fitted to the text in `source`), `applyPatch` returns false.
|
||||
*
|
||||
* @param patch a string diff or the output from the `parsePatch` or `structuredPatch` methods.
|
||||
*/
|
||||
export function applyPatch(source, patch, options = {}) {
|
||||
let patches;
|
||||
if (typeof patch === 'string') {
|
||||
patches = parsePatch(patch);
|
||||
}
|
||||
else if (Array.isArray(patch)) {
|
||||
patches = patch;
|
||||
}
|
||||
else {
|
||||
patches = [patch];
|
||||
}
|
||||
if (patches.length > 1) {
|
||||
throw new Error('applyPatch only works with a single input.');
|
||||
}
|
||||
return applyStructuredPatch(source, patches[0], options);
|
||||
}
|
||||
function applyStructuredPatch(source, patch, options = {}) {
|
||||
if (options.autoConvertLineEndings || options.autoConvertLineEndings == null) {
|
||||
if (hasOnlyWinLineEndings(source) && isUnix(patch)) {
|
||||
patch = unixToWin(patch);
|
||||
}
|
||||
else if (hasOnlyUnixLineEndings(source) && isWin(patch)) {
|
||||
patch = winToUnix(patch);
|
||||
}
|
||||
}
|
||||
// Apply the diff to the input
|
||||
const lines = source.split('\n'), hunks = patch.hunks, compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), fuzzFactor = options.fuzzFactor || 0;
|
||||
let minLine = 0;
|
||||
if (fuzzFactor < 0 || !Number.isInteger(fuzzFactor)) {
|
||||
throw new Error('fuzzFactor must be a non-negative integer');
|
||||
}
|
||||
// Special case for empty patch.
|
||||
if (!hunks.length) {
|
||||
return source;
|
||||
}
|
||||
// Before anything else, handle EOFNL insertion/removal. If the patch tells us to make a change
|
||||
// to the EOFNL that is redundant/impossible - i.e. to remove a newline that's not there, or add a
|
||||
// newline that already exists - then we either return false and fail to apply the patch (if
|
||||
// fuzzFactor is 0) or simply ignore the problem and do nothing (if fuzzFactor is >0).
|
||||
// If we do need to remove/add a newline at EOF, this will always be in the final hunk:
|
||||
let prevLine = '', removeEOFNL = false, addEOFNL = false;
|
||||
for (let i = 0; i < hunks[hunks.length - 1].lines.length; i++) {
|
||||
const line = hunks[hunks.length - 1].lines[i];
|
||||
if (line[0] == '\\') {
|
||||
if (prevLine[0] == '+') {
|
||||
removeEOFNL = true;
|
||||
}
|
||||
else if (prevLine[0] == '-') {
|
||||
addEOFNL = true;
|
||||
}
|
||||
}
|
||||
prevLine = line;
|
||||
}
|
||||
if (removeEOFNL) {
|
||||
if (addEOFNL) {
|
||||
// This means the final line gets changed but doesn't have a trailing newline in either the
|
||||
// original or patched version. In that case, we do nothing if fuzzFactor > 0, and if
|
||||
// fuzzFactor is 0, we simply validate that the source file has no trailing newline.
|
||||
if (!fuzzFactor && lines[lines.length - 1] == '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (lines[lines.length - 1] == '') {
|
||||
lines.pop();
|
||||
}
|
||||
else if (!fuzzFactor) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (addEOFNL) {
|
||||
if (lines[lines.length - 1] != '') {
|
||||
lines.push('');
|
||||
}
|
||||
else if (!fuzzFactor) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks if the hunk can be made to fit at the provided location with at most `maxErrors`
|
||||
* insertions, substitutions, or deletions, while ensuring also that:
|
||||
* - lines deleted in the hunk match exactly, and
|
||||
* - wherever an insertion operation or block of insertion operations appears in the hunk, the
|
||||
* immediately preceding and following lines of context match exactly
|
||||
*
|
||||
* `toPos` should be set such that lines[toPos] is meant to match hunkLines[0].
|
||||
*
|
||||
* If the hunk can be applied, returns an object with properties `oldLineLastI` and
|
||||
* `replacementLines`. Otherwise, returns null.
|
||||
*/
|
||||
function applyHunk(hunkLines, toPos, maxErrors, hunkLinesI = 0, lastContextLineMatched = true, patchedLines = [], patchedLinesLength = 0) {
|
||||
let nConsecutiveOldContextLines = 0;
|
||||
let nextContextLineMustMatch = false;
|
||||
for (; hunkLinesI < hunkLines.length; hunkLinesI++) {
|
||||
const hunkLine = hunkLines[hunkLinesI], operation = (hunkLine.length > 0 ? hunkLine[0] : ' '), content = (hunkLine.length > 0 ? hunkLine.substr(1) : hunkLine);
|
||||
if (operation === '-') {
|
||||
if (compareLine(toPos + 1, lines[toPos], operation, content)) {
|
||||
toPos++;
|
||||
nConsecutiveOldContextLines = 0;
|
||||
}
|
||||
else {
|
||||
if (!maxErrors || lines[toPos] == null) {
|
||||
return null;
|
||||
}
|
||||
patchedLines[patchedLinesLength] = lines[toPos];
|
||||
return applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1);
|
||||
}
|
||||
}
|
||||
if (operation === '+') {
|
||||
if (!lastContextLineMatched) {
|
||||
return null;
|
||||
}
|
||||
patchedLines[patchedLinesLength] = content;
|
||||
patchedLinesLength++;
|
||||
nConsecutiveOldContextLines = 0;
|
||||
nextContextLineMustMatch = true;
|
||||
}
|
||||
if (operation === ' ') {
|
||||
nConsecutiveOldContextLines++;
|
||||
patchedLines[patchedLinesLength] = lines[toPos];
|
||||
if (compareLine(toPos + 1, lines[toPos], operation, content)) {
|
||||
patchedLinesLength++;
|
||||
lastContextLineMatched = true;
|
||||
nextContextLineMustMatch = false;
|
||||
toPos++;
|
||||
}
|
||||
else {
|
||||
if (nextContextLineMustMatch || !maxErrors) {
|
||||
return null;
|
||||
}
|
||||
// Consider 3 possibilities in sequence:
|
||||
// 1. lines contains a *substitution* not included in the patch context, or
|
||||
// 2. lines contains an *insertion* not included in the patch context, or
|
||||
// 3. lines contains a *deletion* not included in the patch context
|
||||
// The first two options are of course only possible if the line from lines is non-null -
|
||||
// i.e. only option 3 is possible if we've overrun the end of the old file.
|
||||
return (lines[toPos] && (applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength + 1) || applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1)) || applyHunk(hunkLines, toPos, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Before returning, trim any unmodified context lines off the end of patchedLines and reduce
|
||||
// toPos (and thus oldLineLastI) accordingly. This allows later hunks to be applied to a region
|
||||
// that starts in this hunk's trailing context.
|
||||
patchedLinesLength -= nConsecutiveOldContextLines;
|
||||
toPos -= nConsecutiveOldContextLines;
|
||||
patchedLines.length = patchedLinesLength;
|
||||
return {
|
||||
patchedLines,
|
||||
oldLineLastI: toPos - 1
|
||||
};
|
||||
}
|
||||
const resultLines = [];
|
||||
// Search best fit offsets for each hunk based on the previous ones
|
||||
let prevHunkOffset = 0;
|
||||
for (let i = 0; i < hunks.length; i++) {
|
||||
const hunk = hunks[i];
|
||||
let hunkResult;
|
||||
const maxLine = lines.length - hunk.oldLines + fuzzFactor;
|
||||
let toPos;
|
||||
for (let maxErrors = 0; maxErrors <= fuzzFactor; maxErrors++) {
|
||||
toPos = hunk.oldStart + prevHunkOffset - 1;
|
||||
const iterator = distanceIterator(toPos, minLine, maxLine);
|
||||
for (; toPos !== undefined; toPos = iterator()) {
|
||||
hunkResult = applyHunk(hunk.lines, toPos, maxErrors);
|
||||
if (hunkResult) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hunkResult) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hunkResult) {
|
||||
return false;
|
||||
}
|
||||
// Copy everything from the end of where we applied the last hunk to the start of this hunk
|
||||
for (let i = minLine; i < toPos; i++) {
|
||||
resultLines.push(lines[i]);
|
||||
}
|
||||
// Add the lines produced by applying the hunk:
|
||||
for (let i = 0; i < hunkResult.patchedLines.length; i++) {
|
||||
const line = hunkResult.patchedLines[i];
|
||||
resultLines.push(line);
|
||||
}
|
||||
// Set lower text limit to end of the current hunk, so next ones don't try
|
||||
// to fit over already patched text
|
||||
minLine = hunkResult.oldLineLastI + 1;
|
||||
// Note the offset between where the patch said the hunk should've applied and where we
|
||||
// applied it, so we can adjust future hunks accordingly:
|
||||
prevHunkOffset = toPos + 1 - hunk.oldStart;
|
||||
}
|
||||
// Copy over the rest of the lines from the old text
|
||||
for (let i = minLine; i < lines.length; i++) {
|
||||
resultLines.push(lines[i]);
|
||||
}
|
||||
return resultLines.join('\n');
|
||||
}
|
||||
/**
|
||||
* applies one or more patches.
|
||||
*
|
||||
* `patch` may be either an array of structured patch objects, or a string representing a patch in unified diff format (which may patch one or more files).
|
||||
*
|
||||
* This method will iterate over the contents of the patch and apply to data provided through callbacks. The general flow for each patch index is:
|
||||
*
|
||||
* - `options.loadFile(index, callback)` is called. The caller should then load the contents of the file and then pass that to the `callback(err, data)` callback. Passing an `err` will terminate further patch execution.
|
||||
* - `options.patched(index, content, callback)` is called once the patch has been applied. `content` will be the return value from `applyPatch`. When it's ready, the caller should call `callback(err)` callback. Passing an `err` will terminate further patch execution.
|
||||
*
|
||||
* Once all patches have been applied or an error occurs, the `options.complete(err)` callback is made.
|
||||
*/
|
||||
export function applyPatches(uniDiff, options) {
|
||||
const spDiff = typeof uniDiff === 'string' ? parsePatch(uniDiff) : uniDiff;
|
||||
let currentIndex = 0;
|
||||
function processIndex() {
|
||||
const index = spDiff[currentIndex++];
|
||||
if (!index) {
|
||||
return options.complete();
|
||||
}
|
||||
options.loadFile(index, function (err, data) {
|
||||
if (err) {
|
||||
return options.complete(err);
|
||||
}
|
||||
const updatedContent = applyPatch(data, index, options);
|
||||
options.patched(index, updatedContent, function (err) {
|
||||
if (err) {
|
||||
return options.complete(err);
|
||||
}
|
||||
processIndex();
|
||||
});
|
||||
});
|
||||
}
|
||||
processIndex();
|
||||
}
|
||||
Generated
Vendored
+228
@@ -0,0 +1,228 @@
|
||||
import { diffLines } from '../diff/line.js';
|
||||
export const INCLUDE_HEADERS = {
|
||||
includeIndex: true,
|
||||
includeUnderline: true,
|
||||
includeFileHeaders: true
|
||||
};
|
||||
export const FILE_HEADERS_ONLY = {
|
||||
includeIndex: false,
|
||||
includeUnderline: false,
|
||||
includeFileHeaders: true
|
||||
};
|
||||
export const OMIT_HEADERS = {
|
||||
includeIndex: false,
|
||||
includeUnderline: false,
|
||||
includeFileHeaders: false
|
||||
};
|
||||
export function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
|
||||
let optionsObj;
|
||||
if (!options) {
|
||||
optionsObj = {};
|
||||
}
|
||||
else if (typeof options === 'function') {
|
||||
optionsObj = { callback: options };
|
||||
}
|
||||
else {
|
||||
optionsObj = options;
|
||||
}
|
||||
if (typeof optionsObj.context === 'undefined') {
|
||||
optionsObj.context = 4;
|
||||
}
|
||||
// We copy this into its own variable to placate TypeScript, which thinks
|
||||
// optionsObj.context might be undefined in the callbacks below.
|
||||
const context = optionsObj.context;
|
||||
// @ts-expect-error (runtime check for something that is correctly a static type error)
|
||||
if (optionsObj.newlineIsToken) {
|
||||
throw new Error('newlineIsToken may not be used with patch-generation functions, only with diffing functions');
|
||||
}
|
||||
if (!optionsObj.callback) {
|
||||
return diffLinesResultToPatch(diffLines(oldStr, newStr, optionsObj));
|
||||
}
|
||||
else {
|
||||
const { callback } = optionsObj;
|
||||
diffLines(oldStr, newStr, Object.assign(Object.assign({}, optionsObj), { callback: (diff) => {
|
||||
const patch = diffLinesResultToPatch(diff);
|
||||
// TypeScript is unhappy without the cast because it does not understand that `patch` may
|
||||
// be undefined here only if `callback` is StructuredPatchCallbackAbortable:
|
||||
callback(patch);
|
||||
} }));
|
||||
}
|
||||
function diffLinesResultToPatch(diff) {
|
||||
// STEP 1: Build up the patch with no "\ No newline at end of file" lines and with the arrays
|
||||
// of lines containing trailing newline characters. We'll tidy up later...
|
||||
if (!diff) {
|
||||
return;
|
||||
}
|
||||
diff.push({ value: '', lines: [] }); // Append an empty value to make cleanup easier
|
||||
function contextLines(lines) {
|
||||
return lines.map(function (entry) { return ' ' + entry; });
|
||||
}
|
||||
const hunks = [];
|
||||
let oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1;
|
||||
for (let i = 0; i < diff.length; i++) {
|
||||
const current = diff[i], lines = current.lines || splitLines(current.value);
|
||||
current.lines = lines;
|
||||
if (current.added || current.removed) {
|
||||
// If we have previous context, start with that
|
||||
if (!oldRangeStart) {
|
||||
const prev = diff[i - 1];
|
||||
oldRangeStart = oldLine;
|
||||
newRangeStart = newLine;
|
||||
if (prev) {
|
||||
curRange = context > 0 ? contextLines(prev.lines.slice(-context)) : [];
|
||||
oldRangeStart -= curRange.length;
|
||||
newRangeStart -= curRange.length;
|
||||
}
|
||||
}
|
||||
// Output our changes
|
||||
for (const line of lines) {
|
||||
curRange.push((current.added ? '+' : '-') + line);
|
||||
}
|
||||
// Track the updated file position
|
||||
if (current.added) {
|
||||
newLine += lines.length;
|
||||
}
|
||||
else {
|
||||
oldLine += lines.length;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Identical context lines. Track line changes
|
||||
if (oldRangeStart) {
|
||||
// Close out any changes that have been output (or join overlapping)
|
||||
if (lines.length <= context * 2 && i < diff.length - 2) {
|
||||
// Overlapping
|
||||
for (const line of contextLines(lines)) {
|
||||
curRange.push(line);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// end the range and output
|
||||
const contextSize = Math.min(lines.length, context);
|
||||
for (const line of contextLines(lines.slice(0, contextSize))) {
|
||||
curRange.push(line);
|
||||
}
|
||||
const hunk = {
|
||||
oldStart: oldRangeStart,
|
||||
oldLines: (oldLine - oldRangeStart + contextSize),
|
||||
newStart: newRangeStart,
|
||||
newLines: (newLine - newRangeStart + contextSize),
|
||||
lines: curRange
|
||||
};
|
||||
hunks.push(hunk);
|
||||
oldRangeStart = 0;
|
||||
newRangeStart = 0;
|
||||
curRange = [];
|
||||
}
|
||||
}
|
||||
oldLine += lines.length;
|
||||
newLine += lines.length;
|
||||
}
|
||||
}
|
||||
// Step 2: eliminate the trailing `\n` from each line of each hunk, and, where needed, add
|
||||
// "\ No newline at end of file".
|
||||
for (const hunk of hunks) {
|
||||
for (let i = 0; i < hunk.lines.length; i++) {
|
||||
if (hunk.lines[i].endsWith('\n')) {
|
||||
hunk.lines[i] = hunk.lines[i].slice(0, -1);
|
||||
}
|
||||
else {
|
||||
hunk.lines.splice(i + 1, 0, '\\ No newline at end of file');
|
||||
i++; // Skip the line we just added, then continue iterating
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
oldFileName: oldFileName, newFileName: newFileName,
|
||||
oldHeader: oldHeader, newHeader: newHeader,
|
||||
hunks: hunks
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* creates a unified diff patch.
|
||||
* @param patch either a single structured patch object (as returned by `structuredPatch`) or an array of them (as returned by `parsePatch`)
|
||||
*/
|
||||
export function formatPatch(patch, headerOptions) {
|
||||
if (!headerOptions) {
|
||||
headerOptions = INCLUDE_HEADERS;
|
||||
}
|
||||
if (Array.isArray(patch)) {
|
||||
if (patch.length > 1 && !headerOptions.includeFileHeaders) {
|
||||
throw new Error('Cannot omit file headers on a multi-file patch. '
|
||||
+ '(The result would be unparseable; how would a tool trying to apply '
|
||||
+ 'the patch know which changes are to which file?)');
|
||||
}
|
||||
return patch.map(p => formatPatch(p, headerOptions)).join('\n');
|
||||
}
|
||||
const ret = [];
|
||||
if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName) {
|
||||
ret.push('Index: ' + patch.oldFileName);
|
||||
}
|
||||
if (headerOptions.includeUnderline) {
|
||||
ret.push('===================================================================');
|
||||
}
|
||||
if (headerOptions.includeFileHeaders) {
|
||||
ret.push('--- ' + patch.oldFileName + (typeof patch.oldHeader === 'undefined' ? '' : '\t' + patch.oldHeader));
|
||||
ret.push('+++ ' + patch.newFileName + (typeof patch.newHeader === 'undefined' ? '' : '\t' + patch.newHeader));
|
||||
}
|
||||
for (let i = 0; i < patch.hunks.length; i++) {
|
||||
const hunk = patch.hunks[i];
|
||||
// Unified Diff Format quirk: If the chunk size is 0,
|
||||
// the first number is one lower than one would expect.
|
||||
// https://www.artima.com/weblogs/viewpost.jsp?thread=164293
|
||||
if (hunk.oldLines === 0) {
|
||||
hunk.oldStart -= 1;
|
||||
}
|
||||
if (hunk.newLines === 0) {
|
||||
hunk.newStart -= 1;
|
||||
}
|
||||
ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines
|
||||
+ ' +' + hunk.newStart + ',' + hunk.newLines
|
||||
+ ' @@');
|
||||
for (const line of hunk.lines) {
|
||||
ret.push(line);
|
||||
}
|
||||
}
|
||||
return ret.join('\n') + '\n';
|
||||
}
|
||||
export function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
|
||||
if (typeof options === 'function') {
|
||||
options = { callback: options };
|
||||
}
|
||||
if (!(options === null || options === void 0 ? void 0 : options.callback)) {
|
||||
const patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options);
|
||||
if (!patchObj) {
|
||||
return;
|
||||
}
|
||||
return formatPatch(patchObj, options === null || options === void 0 ? void 0 : options.headerOptions);
|
||||
}
|
||||
else {
|
||||
const { callback } = options;
|
||||
structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, Object.assign(Object.assign({}, options), { callback: patchObj => {
|
||||
if (!patchObj) {
|
||||
callback(undefined);
|
||||
}
|
||||
else {
|
||||
callback(formatPatch(patchObj, options.headerOptions));
|
||||
}
|
||||
} }));
|
||||
}
|
||||
}
|
||||
export function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) {
|
||||
return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options);
|
||||
}
|
||||
/**
|
||||
* Split `text` into an array of lines, including the trailing newline character (where present)
|
||||
*/
|
||||
function splitLines(text) {
|
||||
const hasTrailingNl = text.endsWith('\n');
|
||||
const result = text.split('\n').map(line => line + '\n');
|
||||
if (hasTrailingNl) {
|
||||
result.pop();
|
||||
}
|
||||
else {
|
||||
result.push(result.pop().slice(0, -1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Generated
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
export function unixToWin(patch) {
|
||||
if (Array.isArray(patch)) {
|
||||
// It would be cleaner if instead of the line below we could just write
|
||||
// return patch.map(unixToWin)
|
||||
// but mysteriously TypeScript (v5.7.3 at the time of writing) does not like this and it will
|
||||
// refuse to compile, thinking that unixToWin could then return StructuredPatch[][] and the
|
||||
// result would be incompatible with the overload signatures.
|
||||
// See bug report at https://github.com/microsoft/TypeScript/issues/61398.
|
||||
return patch.map(p => unixToWin(p));
|
||||
}
|
||||
return Object.assign(Object.assign({}, patch), { hunks: patch.hunks.map(hunk => (Object.assign(Object.assign({}, hunk), { lines: hunk.lines.map((line, i) => {
|
||||
var _a;
|
||||
return (line.startsWith('\\') || line.endsWith('\r') || ((_a = hunk.lines[i + 1]) === null || _a === void 0 ? void 0 : _a.startsWith('\\')))
|
||||
? line
|
||||
: line + '\r';
|
||||
}) }))) });
|
||||
}
|
||||
export function winToUnix(patch) {
|
||||
if (Array.isArray(patch)) {
|
||||
// (See comment above equivalent line in unixToWin)
|
||||
return patch.map(p => winToUnix(p));
|
||||
}
|
||||
return Object.assign(Object.assign({}, patch), { hunks: patch.hunks.map(hunk => (Object.assign(Object.assign({}, hunk), { lines: hunk.lines.map(line => line.endsWith('\r') ? line.substring(0, line.length - 1) : line) }))) });
|
||||
}
|
||||
/**
|
||||
* Returns true if the patch consistently uses Unix line endings (or only involves one line and has
|
||||
* no line endings).
|
||||
*/
|
||||
export function isUnix(patch) {
|
||||
if (!Array.isArray(patch)) {
|
||||
patch = [patch];
|
||||
}
|
||||
return !patch.some(index => index.hunks.some(hunk => hunk.lines.some(line => !line.startsWith('\\') && line.endsWith('\r'))));
|
||||
}
|
||||
/**
|
||||
* Returns true if the patch uses Windows line endings and only Windows line endings.
|
||||
*/
|
||||
export function isWin(patch) {
|
||||
if (!Array.isArray(patch)) {
|
||||
patch = [patch];
|
||||
}
|
||||
return patch.some(index => index.hunks.some(hunk => hunk.lines.some(line => line.endsWith('\r'))))
|
||||
&& patch.every(index => index.hunks.every(hunk => hunk.lines.every((line, i) => { var _a; return line.startsWith('\\') || line.endsWith('\r') || ((_a = hunk.lines[i + 1]) === null || _a === void 0 ? void 0 : _a.startsWith('\\')); })));
|
||||
}
|
||||
Generated
Vendored
+147
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Parses a patch into structured data, in the same structure returned by `structuredPatch`.
|
||||
*
|
||||
* @return a JSON object representation of the a patch, suitable for use with the `applyPatch` method.
|
||||
*/
|
||||
export function parsePatch(uniDiff) {
|
||||
const diffstr = uniDiff.split(/\n/), list = [];
|
||||
let i = 0;
|
||||
function parseIndex() {
|
||||
const index = {};
|
||||
list.push(index);
|
||||
// Parse diff metadata
|
||||
while (i < diffstr.length) {
|
||||
const line = diffstr[i];
|
||||
// File header found, end parsing diff metadata
|
||||
if ((/^(---|\+\+\+|@@)\s/).test(line)) {
|
||||
break;
|
||||
}
|
||||
// Try to parse the line as a diff header, like
|
||||
// Index: README.md
|
||||
// or
|
||||
// diff -r 9117c6561b0b -r 273ce12ad8f1 .hgignore
|
||||
// or
|
||||
// Index: something with multiple words
|
||||
// and extract the filename (or whatever else is used as an index name)
|
||||
// from the end (i.e. 'README.md', '.hgignore', or
|
||||
// 'something with multiple words' in the examples above).
|
||||
//
|
||||
// TODO: It seems awkward that we indiscriminately trim off trailing
|
||||
// whitespace here. Theoretically, couldn't that be meaningful -
|
||||
// e.g. if the patch represents a diff of a file whose name ends
|
||||
// with a space? Seems wrong to nuke it.
|
||||
// But this behaviour has been around since v2.2.1 in 2015, so if
|
||||
// it's going to change, it should be done cautiously and in a new
|
||||
// major release, for backwards-compat reasons.
|
||||
// -- ExplodingCabbage
|
||||
const headerMatch = (/^(?:Index:|diff(?: -r \w+)+)\s+/).exec(line);
|
||||
if (headerMatch) {
|
||||
index.index = line.substring(headerMatch[0].length).trim();
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// Parse file headers if they are defined. Unified diff requires them, but
|
||||
// there's no technical issues to have an isolated hunk without file header
|
||||
parseFileHeader(index);
|
||||
parseFileHeader(index);
|
||||
// Parse hunks
|
||||
index.hunks = [];
|
||||
while (i < diffstr.length) {
|
||||
const line = diffstr[i];
|
||||
if ((/^(Index:\s|diff\s|---\s|\+\+\+\s|===================================================================)/).test(line)) {
|
||||
break;
|
||||
}
|
||||
else if ((/^@@/).test(line)) {
|
||||
index.hunks.push(parseHunk());
|
||||
}
|
||||
else if (line) {
|
||||
throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(line));
|
||||
}
|
||||
else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parses the --- and +++ headers, if none are found, no lines
|
||||
// are consumed.
|
||||
function parseFileHeader(index) {
|
||||
const fileHeaderMatch = (/^(---|\+\+\+)\s+/).exec(diffstr[i]);
|
||||
if (fileHeaderMatch) {
|
||||
const prefix = fileHeaderMatch[1], data = diffstr[i].substring(3).trim().split('\t', 2), header = (data[1] || '').trim();
|
||||
let fileName = data[0].replace(/\\\\/g, '\\');
|
||||
if (fileName.startsWith('"') && fileName.endsWith('"')) {
|
||||
fileName = fileName.substr(1, fileName.length - 2);
|
||||
}
|
||||
if (prefix === '---') {
|
||||
index.oldFileName = fileName;
|
||||
index.oldHeader = header;
|
||||
}
|
||||
else {
|
||||
index.newFileName = fileName;
|
||||
index.newHeader = header;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
// Parses a hunk
|
||||
// This assumes that we are at the start of a hunk.
|
||||
function parseHunk() {
|
||||
var _a;
|
||||
const chunkHeaderIndex = i, chunkHeaderLine = diffstr[i++], chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
|
||||
const hunk = {
|
||||
oldStart: +chunkHeader[1],
|
||||
oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2],
|
||||
newStart: +chunkHeader[3],
|
||||
newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4],
|
||||
lines: []
|
||||
};
|
||||
// Unified Diff Format quirk: If the chunk size is 0,
|
||||
// the first number is one lower than one would expect.
|
||||
// https://www.artima.com/weblogs/viewpost.jsp?thread=164293
|
||||
if (hunk.oldLines === 0) {
|
||||
hunk.oldStart += 1;
|
||||
}
|
||||
if (hunk.newLines === 0) {
|
||||
hunk.newStart += 1;
|
||||
}
|
||||
let addCount = 0, removeCount = 0;
|
||||
for (; i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || ((_a = diffstr[i]) === null || _a === void 0 ? void 0 : _a.startsWith('\\'))); i++) {
|
||||
const operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0];
|
||||
if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
|
||||
hunk.lines.push(diffstr[i]);
|
||||
if (operation === '+') {
|
||||
addCount++;
|
||||
}
|
||||
else if (operation === '-') {
|
||||
removeCount++;
|
||||
}
|
||||
else if (operation === ' ') {
|
||||
addCount++;
|
||||
removeCount++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error(`Hunk at line ${chunkHeaderIndex + 1} contained invalid line ${diffstr[i]}`);
|
||||
}
|
||||
}
|
||||
// Handle the empty block count case
|
||||
if (!addCount && hunk.newLines === 1) {
|
||||
hunk.newLines = 0;
|
||||
}
|
||||
if (!removeCount && hunk.oldLines === 1) {
|
||||
hunk.oldLines = 0;
|
||||
}
|
||||
// Perform sanity checking
|
||||
if (addCount !== hunk.newLines) {
|
||||
throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
|
||||
}
|
||||
if (removeCount !== hunk.oldLines) {
|
||||
throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
|
||||
}
|
||||
return hunk;
|
||||
}
|
||||
while (i < diffstr.length) {
|
||||
parseIndex();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
Generated
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
export function reversePatch(structuredPatch) {
|
||||
if (Array.isArray(structuredPatch)) {
|
||||
// (See comment in unixToWin for why we need the pointless-looking anonymous function here)
|
||||
return structuredPatch.map(patch => reversePatch(patch)).reverse();
|
||||
}
|
||||
return Object.assign(Object.assign({}, structuredPatch), { oldFileName: structuredPatch.newFileName, oldHeader: structuredPatch.newHeader, newFileName: structuredPatch.oldFileName, newHeader: structuredPatch.oldHeader, hunks: structuredPatch.hunks.map(hunk => {
|
||||
return {
|
||||
oldLines: hunk.newLines,
|
||||
oldStart: hunk.newStart,
|
||||
newLines: hunk.oldLines,
|
||||
newStart: hunk.oldStart,
|
||||
lines: hunk.lines.map(l => {
|
||||
if (l.startsWith('-')) {
|
||||
return `+${l.slice(1)}`;
|
||||
}
|
||||
if (l.startsWith('+')) {
|
||||
return `-${l.slice(1)}`;
|
||||
}
|
||||
return l;
|
||||
})
|
||||
};
|
||||
}) });
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
export function arrayEqual(a, b) {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
return arrayStartsWith(a, b);
|
||||
}
|
||||
export function arrayStartsWith(array, start) {
|
||||
if (start.length > array.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < start.length; i++) {
|
||||
if (start[i] !== array[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Generated
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
// Iterator that traverses in the range of [min, max], stepping
|
||||
// by distance from a given start position. I.e. for [0, 4], with
|
||||
// start of 2, this will iterate 2, 3, 1, 4, 0.
|
||||
export default function (start, minLine, maxLine) {
|
||||
let wantForward = true, backwardExhausted = false, forwardExhausted = false, localOffset = 1;
|
||||
return function iterator() {
|
||||
if (wantForward && !forwardExhausted) {
|
||||
if (backwardExhausted) {
|
||||
localOffset++;
|
||||
}
|
||||
else {
|
||||
wantForward = false;
|
||||
}
|
||||
// Check if trying to fit beyond text length, and if not, check it fits
|
||||
// after offset location (or desired location on first iteration)
|
||||
if (start + localOffset <= maxLine) {
|
||||
return start + localOffset;
|
||||
}
|
||||
forwardExhausted = true;
|
||||
}
|
||||
if (!backwardExhausted) {
|
||||
if (!forwardExhausted) {
|
||||
wantForward = true;
|
||||
}
|
||||
// Check if trying to fit before text beginning, and if not, check it fits
|
||||
// before offset location
|
||||
if (minLine <= start - localOffset) {
|
||||
return start - localOffset++;
|
||||
}
|
||||
backwardExhausted = true;
|
||||
return iterator();
|
||||
}
|
||||
// We tried to fit hunk before text beginning and beyond text length, then
|
||||
// hunk can't fit on the text. Return undefined
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
export function generateOptions(options, defaults) {
|
||||
if (typeof options === 'function') {
|
||||
defaults.callback = options;
|
||||
}
|
||||
else if (options) {
|
||||
for (const name in options) {
|
||||
/* istanbul ignore else */
|
||||
if (Object.prototype.hasOwnProperty.call(options, name)) {
|
||||
defaults[name] = options[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaults;
|
||||
}
|
||||
Generated
Vendored
+184
@@ -0,0 +1,184 @@
|
||||
export function longestCommonPrefix(str1, str2) {
|
||||
let i;
|
||||
for (i = 0; i < str1.length && i < str2.length; i++) {
|
||||
if (str1[i] != str2[i]) {
|
||||
return str1.slice(0, i);
|
||||
}
|
||||
}
|
||||
return str1.slice(0, i);
|
||||
}
|
||||
export function longestCommonSuffix(str1, str2) {
|
||||
let i;
|
||||
// Unlike longestCommonPrefix, we need a special case to handle all scenarios
|
||||
// where we return the empty string since str1.slice(-0) will return the
|
||||
// entire string.
|
||||
if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) {
|
||||
return '';
|
||||
}
|
||||
for (i = 0; i < str1.length && i < str2.length; i++) {
|
||||
if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) {
|
||||
return str1.slice(-i);
|
||||
}
|
||||
}
|
||||
return str1.slice(-i);
|
||||
}
|
||||
export function replacePrefix(string, oldPrefix, newPrefix) {
|
||||
if (string.slice(0, oldPrefix.length) != oldPrefix) {
|
||||
throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`);
|
||||
}
|
||||
return newPrefix + string.slice(oldPrefix.length);
|
||||
}
|
||||
export function replaceSuffix(string, oldSuffix, newSuffix) {
|
||||
if (!oldSuffix) {
|
||||
return string + newSuffix;
|
||||
}
|
||||
if (string.slice(-oldSuffix.length) != oldSuffix) {
|
||||
throw Error(`string ${JSON.stringify(string)} doesn't end with suffix ${JSON.stringify(oldSuffix)}; this is a bug`);
|
||||
}
|
||||
return string.slice(0, -oldSuffix.length) + newSuffix;
|
||||
}
|
||||
export function removePrefix(string, oldPrefix) {
|
||||
return replacePrefix(string, oldPrefix, '');
|
||||
}
|
||||
export function removeSuffix(string, oldSuffix) {
|
||||
return replaceSuffix(string, oldSuffix, '');
|
||||
}
|
||||
export function maximumOverlap(string1, string2) {
|
||||
return string2.slice(0, overlapCount(string1, string2));
|
||||
}
|
||||
// Nicked from https://stackoverflow.com/a/60422853/1709587
|
||||
function overlapCount(a, b) {
|
||||
// Deal with cases where the strings differ in length
|
||||
let startA = 0;
|
||||
if (a.length > b.length) {
|
||||
startA = a.length - b.length;
|
||||
}
|
||||
let endB = b.length;
|
||||
if (a.length < b.length) {
|
||||
endB = a.length;
|
||||
}
|
||||
// Create a back-reference for each index
|
||||
// that should be followed in case of a mismatch.
|
||||
// We only need B to make these references:
|
||||
const map = Array(endB);
|
||||
let k = 0; // Index that lags behind j
|
||||
map[0] = 0;
|
||||
for (let j = 1; j < endB; j++) {
|
||||
if (b[j] == b[k]) {
|
||||
map[j] = map[k]; // skip over the same character (optional optimisation)
|
||||
}
|
||||
else {
|
||||
map[j] = k;
|
||||
}
|
||||
while (k > 0 && b[j] != b[k]) {
|
||||
k = map[k];
|
||||
}
|
||||
if (b[j] == b[k]) {
|
||||
k++;
|
||||
}
|
||||
}
|
||||
// Phase 2: use these references while iterating over A
|
||||
k = 0;
|
||||
for (let i = startA; i < a.length; i++) {
|
||||
while (k > 0 && a[i] != b[k]) {
|
||||
k = map[k];
|
||||
}
|
||||
if (a[i] == b[k]) {
|
||||
k++;
|
||||
}
|
||||
}
|
||||
return k;
|
||||
}
|
||||
/**
|
||||
* Returns true if the string consistently uses Windows line endings.
|
||||
*/
|
||||
export function hasOnlyWinLineEndings(string) {
|
||||
return string.includes('\r\n') && !string.startsWith('\n') && !string.match(/[^\r]\n/);
|
||||
}
|
||||
/**
|
||||
* Returns true if the string consistently uses Unix line endings.
|
||||
*/
|
||||
export function hasOnlyUnixLineEndings(string) {
|
||||
return !string.includes('\r\n') && string.includes('\n');
|
||||
}
|
||||
/**
|
||||
* Split a string into segments using a word segmenter, merging consecutive
|
||||
* segments if they are both whitespace segments. Whitespace segments can
|
||||
* appear adjacent to one another for two reasons:
|
||||
* - newlines always get their own segment
|
||||
* - where a diacritic is attached to a whitespace character in the text, the
|
||||
* segment ends after the diacritic, so e.g. " \u0300 " becomes two segments.
|
||||
* This function therefore runs the segmenter's .segment() method and then
|
||||
* merges consecutive segments of whitespace into a single part.
|
||||
*/
|
||||
export function segment(string, segmenter) {
|
||||
const parts = [];
|
||||
for (const segmentObj of Array.from(segmenter.segment(string))) {
|
||||
const segment = segmentObj.segment;
|
||||
if (parts.length && (/\s/).test(parts[parts.length - 1]) && (/\s/).test(segment)) {
|
||||
parts[parts.length - 1] += segment;
|
||||
}
|
||||
else {
|
||||
parts.push(segment);
|
||||
}
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
// The functions below take a `segmenter` argument so that, when called from
|
||||
// diffWords when it is using a segmenter, they can use a notion of what
|
||||
// constitutes "whitespace" that is consistent with the segmenter.
|
||||
//
|
||||
// USUALLY this will be identical to the result of the non-segmenter-based
|
||||
// logic, but it differs in at least one case: when whitespace characters are
|
||||
// modified by diacritics. A word segmenter considers these diacritics to be
|
||||
// part of the whitespace, whereas our non-segmenter-based logic does not.
|
||||
//
|
||||
// Because the segmenter-based approach necessarily requires segmenting the
|
||||
// entire string, we offer a leadingAndTrailingWs function to allow getting the
|
||||
// whitespace prefix AND whitespace suffix with a single call to the segmenter,
|
||||
// for efficiency's sake.
|
||||
export function trailingWs(string, segmenter) {
|
||||
if (segmenter) {
|
||||
return leadingAndTrailingWs(string, segmenter)[1];
|
||||
}
|
||||
// Yes, this looks overcomplicated and dumb - why not replace the whole function with
|
||||
// return string.match(/\s*$/)[0]
|
||||
// you ask? Because:
|
||||
// 1. the trap described at https://markamery.com/blog/quadratic-time-regexes/ would mean doing
|
||||
// this would cause this function to take O(n²) time in the worst case (specifically when
|
||||
// there is a massive run of NON-TRAILING whitespace in `string`), and
|
||||
// 2. the fix proposed in the same blog post, of using a negative lookbehind, is incompatible
|
||||
// with old Safari versions that we'd like to not break if possible (see
|
||||
// https://github.com/kpdecker/jsdiff/pull/550)
|
||||
// It feels absurd to do this with an explicit loop instead of a regex, but I really can't see a
|
||||
// better way that doesn't result in broken behaviour.
|
||||
let i;
|
||||
for (i = string.length - 1; i >= 0; i--) {
|
||||
if (!string[i].match(/\s/)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return string.substring(i + 1);
|
||||
}
|
||||
export function leadingWs(string, segmenter) {
|
||||
if (segmenter) {
|
||||
return leadingAndTrailingWs(string, segmenter)[0];
|
||||
}
|
||||
// Thankfully the annoying considerations described in trailingWs don't apply here:
|
||||
const match = string.match(/^\s*/);
|
||||
return match ? match[0] : '';
|
||||
}
|
||||
export function leadingAndTrailingWs(string, segmenter) {
|
||||
if (!segmenter) {
|
||||
return [leadingWs(string), trailingWs(string)];
|
||||
}
|
||||
if (segmenter.resolvedOptions().granularity != 'word') {
|
||||
throw new Error('The segmenter passed must have a granularity of "word"');
|
||||
}
|
||||
const segments = segment(string, segmenter);
|
||||
const firstSeg = segments[0];
|
||||
const lastSeg = segments[segments.length - 1];
|
||||
const head = (/\s/).test(firstSeg) ? firstSeg : '';
|
||||
const tail = (/\s/).test(lastSeg) ? lastSeg : '';
|
||||
return [head, tail];
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"name": "diff",
|
||||
"version": "8.0.4",
|
||||
"description": "A JavaScript text diff implementation.",
|
||||
"keywords": [
|
||||
"diff",
|
||||
"jsdiff",
|
||||
"compare",
|
||||
"patch",
|
||||
"text",
|
||||
"json",
|
||||
"css",
|
||||
"javascript"
|
||||
],
|
||||
"maintainers": [
|
||||
"Kevin Decker <kpdecker@gmail.com> (http://incaseofstairs.com)",
|
||||
"Mark Amery <markrobertamery+jsdiff@gmail.com>"
|
||||
],
|
||||
"bugs": {
|
||||
"email": "kpdecker@gmail.com",
|
||||
"url": "http://github.com/kpdecker/jsdiff/issues"
|
||||
},
|
||||
"license": "BSD-3-Clause",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/kpdecker/jsdiff.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
},
|
||||
"main": "./libcjs/index.js",
|
||||
"module": "./libesm/index.js",
|
||||
"browser": "./dist/diff.js",
|
||||
"unpkg": "./dist/diff.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./libesm/index.d.ts",
|
||||
"default": "./libesm/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./libcjs/index.d.ts",
|
||||
"default": "./libcjs/index.js"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
"./lib/*.js": {
|
||||
"import": {
|
||||
"types": "./libesm/*.d.ts",
|
||||
"default": "./libesm/*.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./libcjs/*.d.ts",
|
||||
"default": "./libcjs/*.js"
|
||||
}
|
||||
},
|
||||
"./lib/": {
|
||||
"import": {
|
||||
"types": "./libesm/",
|
||||
"default": "./libesm/"
|
||||
},
|
||||
"require": {
|
||||
"types": "./libcjs/",
|
||||
"default": "./libcjs/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "module",
|
||||
"types": "libcjs/index.d.ts",
|
||||
"scripts": {
|
||||
"clean": "rm -rf libcjs/ libesm/ dist/ coverage/ .nyc_output/",
|
||||
"lint": "yarn eslint",
|
||||
"build": "yarn lint && yarn generate-esm && yarn generate-cjs && yarn check-types && yarn run-rollup && yarn run-uglify",
|
||||
"generate-cjs": "yarn tsc --module commonjs --outDir libcjs && node --eval \"fs.writeFileSync('libcjs/package.json', JSON.stringify({type:'commonjs',sideEffects:false}))\"",
|
||||
"generate-esm": "yarn tsc --module nodenext --outDir libesm --target es6 && node --eval \"fs.writeFileSync('libesm/package.json', JSON.stringify({type:'module',sideEffects:false}))\"",
|
||||
"check-types": "yarn run-tsd && yarn run-attw",
|
||||
"test": "nyc yarn _test",
|
||||
"_test": "yarn build && cross-env NODE_ENV=test yarn run-mocha",
|
||||
"run-attw": "yarn attw --pack --entrypoints . && yarn attw --pack --entrypoints lib/diff/word.js --profile node16",
|
||||
"run-tsd": "yarn tsd --typings libesm/ && yarn tsd --files test-d/",
|
||||
"run-rollup": "rollup -c rollup.config.mjs",
|
||||
"run-uglify": "uglifyjs dist/diff.js -c -o dist/diff.min.js",
|
||||
"run-mocha": "mocha --require ./runtime 'test/**/*.js'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "^0.18.2",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.2",
|
||||
"@babel/register": "^7.28.6",
|
||||
"@colors/colors": "^1.6.0",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"babel-loader": "^10.1.1",
|
||||
"babel-plugin-istanbul": "^7.0.1",
|
||||
"chai": "^6.2.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^10.1.0",
|
||||
"globals": "^17.4.0",
|
||||
"karma": "^6.4.4",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-sourcemap-loader": "^0.4.0",
|
||||
"karma-webpack": "^5.0.1",
|
||||
"mocha": "^11.7.5",
|
||||
"nyc": "^18.0.0",
|
||||
"rollup": "^4.60.0",
|
||||
"tsd": "^0.33.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.57.1",
|
||||
"uglify-js": "^3.19.3",
|
||||
"webpack": "^5.105.4",
|
||||
"webpack-dev-server": "^5.2.3"
|
||||
},
|
||||
"nyc": {
|
||||
"require": [
|
||||
"@babel/register"
|
||||
],
|
||||
"reporter": [
|
||||
"lcov",
|
||||
"text"
|
||||
],
|
||||
"sourceMap": false,
|
||||
"instrument": false,
|
||||
"check-coverage": true,
|
||||
"branches": 100,
|
||||
"lines": 100,
|
||||
"functions": 100,
|
||||
"statements": 100
|
||||
},
|
||||
"packageManager": "yarn@4.12.0"
|
||||
}
|
||||
+432
@@ -0,0 +1,432 @@
|
||||
# Release Notes
|
||||
|
||||
## 8.0.4
|
||||
|
||||
- [#667](https://github.com/kpdecker/jsdiff/pull/667) - **fix another bug in `diffWords` when used with an `Intl.Segmenter`**. If the text to be diffed included a combining mark after a whitespace character (i.e. roughly speaking, an accented space), `diffWords` would previously crash. Now this case is handled correctly.
|
||||
|
||||
## 8.0.3
|
||||
|
||||
- [#631](https://github.com/kpdecker/jsdiff/pull/631) - **fix support for using an `Intl.Segmenter` with `diffWords`**. This has been almost completely broken since the feature was added in v6.0.0, since it would outright crash on any text that featured two consecutive newlines between a pair of words (a very common case).
|
||||
- [#635](https://github.com/kpdecker/jsdiff/pull/635) - **small tweaks to tokenization behaviour of `diffWords`** when used *without* an `Intl.Segmenter`. Specifically, the soft hyphen (U+00AD) is no longer considered to be a word break, and the multiplication and division signs (`×` and `÷`) are now treated as punctuation instead of as letters / word characters.
|
||||
- [#641](https://github.com/kpdecker/jsdiff/pull/641) - **the format of file headers in `createPatch` etc. patches can now be customised somewhat**. It now takes a `headerOptions` option that can be used to disable the file headers entirely, or omit the `Index:` line and/or the underline. In particular, this was motivated by a request to make jsdiff patches compatible with react-diff-view, which they now are if produced with `headerOptions: FILE_HEADERS_ONLY`.
|
||||
- [#647](https://github.com/kpdecker/jsdiff/pull/647) and [#649](https://github.com/kpdecker/jsdiff/pull/649) - **fix denial-of-service vulnerabilities in `parsePatch` whereby adversarial input could cause a memory-leaking infinite loop, typically crashing the calling process**. Also fixed ReDOS vulnerabilities whereby adversarially-crafted patch headers could take cubic time to parse. Now, `parsePatch` should reliably take linear time. (Handling of headers that include the line break characters `\r`, `\u2028`, or `\u2029` in non-trailing positions is also now more reasonable as side effect of the fix.)
|
||||
|
||||
## 8.0.2
|
||||
|
||||
- [#616](https://github.com/kpdecker/jsdiff/pull/616) **Restored compatibility of `diffSentences` with old Safari versions.** This was broken in 8.0.0 by the introduction of a regex with a [lookbehind assertion](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookbehind_assertion); these weren't supported in Safari prior to version 16.4.
|
||||
- [#612](https://github.com/kpdecker/jsdiff/pull/612) **Improved tree shakeability** by marking the built CJS and ESM packages with `sideEffects: false`.
|
||||
|
||||
## 8.0.1
|
||||
|
||||
- [#610](https://github.com/kpdecker/jsdiff/pull/610) **Fixes types for `diffJson` which were broken by 8.0.0**. The new bundled types in 8.0.0 only allowed `diffJson` to be passed string arguments, but it should've been possible to pass either strings or objects (and now is). Thanks to Josh Kelley for the fix.
|
||||
|
||||
## 8.0.0
|
||||
|
||||
- [#580](https://github.com/kpdecker/jsdiff/pull/580) **Multiple tweaks to `diffSentences`**:
|
||||
* tokenization no longer takes quadratic time on pathological inputs (reported as a ReDOS vulnerability by Snyk); is now linear instead
|
||||
* the final sentence in the string is now handled the same by the tokenizer regardless of whether it has a trailing punctuation mark or not. (Previously, "foo. bar." tokenized to `["foo.", " ", "bar."]` but "foo. bar" tokenized to `["foo.", " bar"]` - i.e. whether the space between sentences was treated as a separate token depended upon whether the final sentence had trailing punctuation or not. This was arbitrary and surprising; it is no longer the case.)
|
||||
* in a string that starts with a sentence end, like "! hello.", the "!" is now treated as a separate sentence
|
||||
* the README now correctly documents the tokenization behaviour (it was wrong before)
|
||||
- [#581](https://github.com/kpdecker/jsdiff/pull/581) - **fixed some regex operations used for tokenization in `diffWords` taking O(n^2) time** in pathological cases
|
||||
- [#595](https://github.com/kpdecker/jsdiff/pull/595) - **fixed a crash in patch creation functions when handling a single hunk consisting of a very large number (e.g. >130k) of lines**. (This was caused by spreading indefinitely-large arrays to `.push()` using `.apply` or the spread operator and hitting the JS-implementation-specific limit on the maximum number of arguments to a function, as shown at https://stackoverflow.com/a/56809779/1709587; thus the exact threshold to hit the error will depend on the environment in which you were running JsDiff.)
|
||||
- [#596](https://github.com/kpdecker/jsdiff/pull/596) - **removed the `merge` function**. Previously JsDiff included an undocumented function called `merge` that was meant to, in some sense, merge patches. It had at least a couple of serious bugs that could lead to it returning unambiguously wrong results, and it was difficult to simply "fix" because it was [unclear precisely what it was meant to do](https://github.com/kpdecker/jsdiff/issues/181#issuecomment-2198319542). For now, the fix is to remove it entirely.
|
||||
- [#591](https://github.com/kpdecker/jsdiff/pull/591) - JsDiff's source code has been rewritten in TypeScript. This change entails the following changes for end users:
|
||||
* **the `diff` package on npm now includes its own TypeScript type definitions**. Users who previously used the `@types/diff` npm package from DefinitelyTyped should remove that dependency when upgrading JsDiff to v8.
|
||||
|
||||
Note that the transition from the DefinitelyTyped types to JsDiff's own type definitions includes multiple fixes and also removes many exported types previously used for `options` arguments to diffing and patch-generation functions. (There are now different exported options types for abortable calls - ones with a `timeout` or `maxEditLength` that may give a result of `undefined` - and non-abortable calls.) See the TypeScript section of the README for some usage tips.
|
||||
|
||||
* **The `Diff` object is now a class**. Custom extensions of `Diff`, as described in the "Defining custom diffing behaviors" section of the README, can therefore now be done by writing a `class CustomDiff extends Diff` and overriding methods, instead of the old way based on prototype inheritance. (I *think* code that did things the old way should still work, though!)
|
||||
|
||||
* **`diff/lib/index.es6.js` and `diff/lib/index.mjs` no longer exist, and the ESM version of the library is no longer bundled into a single file.**
|
||||
|
||||
* **The `ignoreWhitespace` option for `diffWords` is no longer included in the type declarations**. The effect of passing `ignoreWhitespace: true` has always been to make `diffWords` just call `diffWordsWithSpace` instead, which was confusing, because that behaviour doesn't seem properly described as "ignoring" whitespace at all. The property remains available to non-TypeScript applications for the sake of backwards compatibility, but TypeScript applications will now see a type error if they try to pass `ignoreWhitespace: true` to `diffWords` and should change their code to call `diffWordsWithSpace` instead.
|
||||
|
||||
* JsDiff no longer purports to support ES3 environments. (I'm pretty sure it never truly did, despite claiming to in its README, since even the 1.0.0 release used `Array.map` which was added in ES5.)
|
||||
- [#601](https://github.com/kpdecker/jsdiff/pull/601) - **`diffJson`'s `stringifyReplacer` option behaves more like `JSON.stringify`'s `replacer` argument now.** In particular:
|
||||
* Each key/value pair now gets passed through the replacer once instead of twice
|
||||
* The `key` passed to the replacer when the top-level object is passed in as `value` is now `""` (previously, was `undefined`), and the `key` passed with an array element is the array index as a string, like `"0"` or `"1"` (previously was whatever the key for the entire array was). Both the new behaviours match that of `JSON.stringify`.
|
||||
- [#602](https://github.com/kpdecker/jsdiff/pull/602) - **diffing functions now consistently return `undefined` when called in async mode** (i.e. with a callback). Previously, there was an odd quirk where they would return `true` if the strings being diffed were equal and `undefined` otherwise.
|
||||
|
||||
## 7.0.0
|
||||
|
||||
Just a single (breaking) bugfix, undoing a behaviour change introduced accidentally in 6.0.0:
|
||||
|
||||
- [#554](https://github.com/kpdecker/jsdiff/pull/554) **`diffWords` treats numbers and underscores as word characters again.** This behaviour was broken in v6.0.0.
|
||||
|
||||
## 6.0.0
|
||||
|
||||
This is a release containing many, *many* breaking changes. The objective of this release was to carry out a mass fix, in one go, of all the open bugs and design problems that required breaking changes to fix. A substantial, but exhaustive, changelog is below.
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v5.2.0...v6.0.0)
|
||||
|
||||
- [#497](https://github.com/kpdecker/jsdiff/pull/497) **`diffWords` behavior has been radically changed.** Previously, even with `ignoreWhitespace: true`, runs of whitespace were tokens, which led to unhelpful and unintuitive diffing behavior in typical texts. Specifically, even when two texts contained overlapping passages, `diffWords` would sometimes choose to delete all the words from the old text and insert them anew in their new positions in order to avoid having to delete or insert whitespace tokens. Whitespace sequences are no longer tokens as of this release, which affects both the generated diffs and the `count`s.
|
||||
|
||||
Runs of whitespace are still tokens in `diffWordsWithSpace`.
|
||||
|
||||
As part of the changes to `diffWords`, **a new `.postProcess` method has been added on the base `Diff` type**, which can be overridden in custom `Diff` implementations.
|
||||
|
||||
**`diffLines` with `ignoreWhitespace: true` will no longer ignore the insertion or deletion of entire extra lines of whitespace at the end of the text**. Previously, these would not show up as insertions or deletions, as a side effect of a hack in the base diffing algorithm meant to help ignore whitespace in `diffWords`. More generally, **the undocumented special handling in the core algorithm for ignored terminals has been removed entirely.** (This special case behavior used to rewrite the final two change objects in a scenario where the final change object was an addition or deletion and its `value` was treated as equal to the empty string when compared using the diff object's `.equals` method.)
|
||||
|
||||
- [#500](https://github.com/kpdecker/jsdiff/pull/500) **`diffChars` now diffs Unicode code points** instead of UTF-16 code units.
|
||||
- [#508](https://github.com/kpdecker/jsdiff/pull/508) **`parsePatch` now always runs in what was previously "strict" mode; the undocumented `strict` option has been removed.** Previously, by default, `parsePatch` (and other patch functions that use it under the hood to parse patches) would accept a patch where the line counts in the headers were inconsistent with the actual patch content - e.g. where a hunk started with the header `@@ -1,3 +1,6 @@`, indicating that the content below spanned 3 lines in the old file and 6 lines in the new file, but then the actual content below the header consisted of some different number of lines, say 10 lines of context, 5 deletions, and 1 insertion. Actually trying to work with these patches using `applyPatch` or `merge`, however, would produce incorrect results instead of just ignoring the incorrect headers, making this "feature" more of a trap than something actually useful. It's been ripped out, and now we are always "strict" and will reject patches where the line counts in the headers aren't consistent with the actual patch content.
|
||||
- [#435](https://github.com/kpdecker/jsdiff/pull/435) **Fix `parsePatch` handling of control characters.** `parsePatch` used to interpret various unusual control characters - namely vertical tabs, form feeds, lone carriage returns without a line feed, and EBCDIC NELs - as line breaks when parsing a patch file. This was inconsistent with the behavior of both JsDiff's own `diffLines` method and also the Unix `diff` and `patch` utils, which all simply treat those control characters as ordinary characters. The result of this discrepancy was that some well-formed patches - produced either by `diff` or by JsDiff itself and handled properly by the `patch` util - would be wrongly parsed by `parsePatch`, with the effect that it would disregard the remainder of a hunk after encountering one of these control characters.
|
||||
- [#439](https://github.com/kpdecker/jsdiff/pull/439) **Prefer diffs that order deletions before insertions.** When faced with a choice between two diffs with an equal total edit distance, the Myers diff algorithm generally prefers one that does deletions before insertions rather than insertions before deletions. For instance, when diffing `abcd` against `acbd`, it will prefer a diff that says to delete the `b` and then insert a new `b` after the `c`, over a diff that says to insert a `c` before the `b` and then delete the existing `c`. JsDiff deviated from the published Myers algorithm in a way that led to it having the opposite preference in many cases, including that example. This is now fixed, meaning diffs output by JsDiff will more accurately reflect what the published Myers diff algorithm would output.
|
||||
- [#455](https://github.com/kpdecker/jsdiff/pull/455) **The `added` and `removed` properties of change objects are now guaranteed to be set to a boolean value.** (Previously, they would be set to `undefined` or omitted entirely instead of setting them to false.)
|
||||
- [#464](https://github.com/kpdecker/jsdiff/pull/464) Specifying `{maxEditLength: 0}` now sets a max edit length of 0 instead of no maximum.
|
||||
- [#460](https://github.com/kpdecker/jsdiff/pull/460) **Added `oneChangePerToken` option.**
|
||||
- [#467](https://github.com/kpdecker/jsdiff/pull/467) **Consistent ordering of arguments to `comparator(left, right)`.** Values from the old array will now consistently be passed as the first argument (`left`) and values from the new array as the second argument (`right`). Previously this was almost (but not quite) always the other way round.
|
||||
- [#480](https://github.com/kpdecker/jsdiff/pull/480) **Passing `maxEditLength` to `createPatch` & `createTwoFilesPatch` now works properly** (i.e. returns undefined if the max edit distance is exceeded; previous behavior was to crash with a `TypeError` if the edit distance was exceeded).
|
||||
- [#486](https://github.com/kpdecker/jsdiff/pull/486) **The `ignoreWhitespace` option of `diffLines` behaves more sensibly now.** `value`s in returned change objects now include leading/trailing whitespace even when `ignoreWhitespace` is used, just like how with `ignoreCase` the `value`s still reflect the case of one of the original texts instead of being all-lowercase. `ignoreWhitespace` is also now compatible with `newlineIsToken`. Finally, **`diffTrimmedLines` is deprecated** (and removed from the docs) in favour of using `diffLines` with `ignoreWhitespace: true`; the two are, and always have been, equivalent.
|
||||
- [#490](https://github.com/kpdecker/jsdiff/pull/490) **When calling diffing functions in async mode by passing a `callback` option, the diff result will now be passed as the *first* argument to the callback instead of the second.** (Previously, the first argument was never used at all and would always have value `undefined`.)
|
||||
- [#489](https://github.com/kpdecker/jsdiff/pull/489) **`this.options` no longer exists on `Diff` objects.** Instead, `options` is now passed as an argument to methods that rely on options, like `equals(left, right, options)`. This fixes a race condition in async mode, where diffing behaviour could be changed mid-execution if a concurrent usage of the same `Diff` instances overwrote its `options`.
|
||||
- [#518](https://github.com/kpdecker/jsdiff/pull/518) **`linedelimiters` no longer exists** on patch objects; instead, when a patch with Windows-style CRLF line endings is parsed, **the lines in `lines` will end with `\r`**. There is now a **new `autoConvertLineEndings` option, on by default**, which makes it so that when a patch with Windows-style line endings is applied to a source file with Unix style line endings, the patch gets autoconverted to use Unix-style line endings, and when a patch with Unix-style line endings is applied to a source file with Windows-style line endings, it gets autoconverted to use Windows-style line endings.
|
||||
- [#521](https://github.com/kpdecker/jsdiff/pull/521) **the `callback` option is now supported by `structuredPatch`, `createPatch`, and `createTwoFilesPatch`**
|
||||
- [#529](https://github.com/kpdecker/jsdiff/pull/529) **`parsePatch` can now parse patches where lines starting with `--` or `++` are deleted/inserted**; previously, there were edge cases where the parser would choke on valid patches or give wrong results.
|
||||
- [#530](https://github.com/kpdecker/jsdiff/pull/530) **Added `ignoreNewlineAtEof` option to `diffLines`**
|
||||
- [#533](https://github.com/kpdecker/jsdiff/pull/533) **`applyPatch` uses an entirely new algorithm for fuzzy matching.** Differences between the old and new algorithm are as follows:
|
||||
* The `fuzzFactor` now indicates the maximum [*Levenshtein* distance](https://en.wikipedia.org/wiki/Levenshtein_distance) that there can be between the context shown in a hunk and the actual file content at a location where we try to apply the hunk. (Previously, it represented a maximum [*Hamming* distance](https://en.wikipedia.org/wiki/Hamming_distance), meaning that a single insertion or deletion in the source file could stop a hunk from applying even with a high `fuzzFactor`.)
|
||||
* A hunk containing a deletion can now only be applied in a context where the line to be deleted actually appears verbatim. (Previously, as long as enough context lines in the hunk matched, `applyPatch` would apply the hunk anyway and delete a completely different line.)
|
||||
* The context line immediately before and immediately after an insertion must match exactly between the hunk and the file for a hunk to apply. (Previously this was not required.)
|
||||
- [#535](https://github.com/kpdecker/jsdiff/pull/535) **A bug in patch generation functions is now fixed** that would sometimes previously cause `\ No newline at end of file` to appear in the wrong place in the generated patch, resulting in the patch being invalid. **These invalid patches can also no longer be applied successfully with `applyPatch`.** (It was already the case that tools other than jsdiff, like GNU `patch`, would consider them malformed and refuse to apply them; versions of jsdiff with this fix now do the same thing if you ask them to apply a malformed patch emitted by jsdiff v5.)
|
||||
- [#535](https://github.com/kpdecker/jsdiff/pull/535) **Passing `newlineIsToken: true` to *patch*-generation functions is no longer allowed.** (Passing it to `diffLines` is still supported - it's only functions like `createPatch` where passing `newlineIsToken` is now an error.) Allowing it to be passed never really made sense, since in cases where the option had any effect on the output at all, the effect tended to be causing a garbled patch to be created that couldn't actually be applied to the source file.
|
||||
- [#539](https://github.com/kpdecker/jsdiff/pull/539) **`diffWords` now takes an optional `intlSegmenter` option** which should be an `Intl.Segmenter` with word-level granularity. This provides better tokenization of text into words than the default behaviour, even for English but especially for some other languages for which the default behaviour is poor.
|
||||
|
||||
## v5.2.2 - January 2026
|
||||
|
||||
Only change from 5.2.0 is a backport of the fix to https://github.com/kpdecker/jsdiff/security/advisories/GHSA-73rr-hh4g-fpgx.
|
||||
|
||||
## v5.2.1 (deprecated)
|
||||
|
||||
Accidental release - do not use.
|
||||
|
||||
## v5.2.0
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v5.1.0...v5.2.0)
|
||||
|
||||
- [#411](https://github.com/kpdecker/jsdiff/pull/411) Big performance improvement. Previously an O(n) array-copying operation inside the innermost loop of jsdiff's base diffing code increased the overall worst-case time complexity of computing a diff from O(n²) to O(n³). This is now fixed, bringing the worst-case time complexity down to what it theoretically should be for a Myers diff implementation.
|
||||
- [#448](https://github.com/kpdecker/jsdiff/pull/448) Performance improvement. Diagonals whose furthest-reaching D-path would go off the edge of the edit graph are now skipped, rather than being pointlessly considered as called for by the original Myers diff algorithm. This dramatically speeds up computing diffs where the new text just appends or truncates content at the end of the old text.
|
||||
- [#351](https://github.com/kpdecker/jsdiff/issues/351) Importing from the lib folder - e.g. `require("diff/lib/diff/word.js")` - will work again now. This had been broken for users on the latest version of Node since Node 17.5.0, which changed how Node interprets the `exports` property in jsdiff's `package.json` file.
|
||||
- [#344](https://github.com/kpdecker/jsdiff/issues/344) `diffLines`, `createTwoFilesPatch`, and other patch-creation methods now take an optional `stripTrailingCr: true` option which causes Windows-style `\r\n` line endings to be replaced with Unix-style `\n` line endings before calculating the diff, just like GNU `diff`'s `--strip-trailing-cr` flag.
|
||||
- [#451](https://github.com/kpdecker/jsdiff/pull/451) Added `diff.formatPatch`.
|
||||
- [#450](https://github.com/kpdecker/jsdiff/pull/450) Added `diff.reversePatch`.
|
||||
- [#478](https://github.com/kpdecker/jsdiff/pull/478) Added `timeout` option.
|
||||
|
||||
## v5.1.0
|
||||
|
||||
- [#365](https://github.com/kpdecker/jsdiff/issues/365) Allow early termination to limit execution time with degenerate cases
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v5.0.0...v5.1.0)
|
||||
|
||||
## v5.0.0
|
||||
|
||||
- Breaking: UMD export renamed from `JsDiff` to `Diff`.
|
||||
- Breaking: Newlines separated into separate tokens for word diff.
|
||||
- Breaking: Unified diffs now match ["quirks"](https://www.artima.com/weblogs/viewpost.jsp?thread=164293)
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v4.0.1...v5.0.0)
|
||||
|
||||
## v4.0.4 - January 2026
|
||||
|
||||
Only change from 4.0.2 is a backport of the fix to https://github.com/kpdecker/jsdiff/security/advisories/GHSA-73rr-hh4g-fpgx.
|
||||
|
||||
## v4.0.3 (deprecated)
|
||||
|
||||
Accidental release - do not use.
|
||||
|
||||
## v4.0.2
|
||||
|
||||
No meaningful changes from v4.0.1 - just removed some cruft that shouldn't've been published.
|
||||
|
||||
## v4.0.1 - January 6th, 2019
|
||||
|
||||
- Fix main reference path - b826104
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v4.0.0...v4.0.1)
|
||||
|
||||
## v4.0.0 - January 5th, 2019
|
||||
|
||||
- [#94](https://github.com/kpdecker/jsdiff/issues/94) - Missing "No newline at end of file" when comparing two texts that do not end in newlines ([@federicotdn](https://api.github.com/users/federicotdn))
|
||||
- [#227](https://github.com/kpdecker/jsdiff/issues/227) - Licence
|
||||
- [#199](https://github.com/kpdecker/jsdiff/issues/199) - Import statement for jsdiff
|
||||
- [#159](https://github.com/kpdecker/jsdiff/issues/159) - applyPatch affecting wrong line number with with new lines
|
||||
- [#8](https://github.com/kpdecker/jsdiff/issues/8) - A new state "replace"
|
||||
- Drop ie9 from karma targets - 79c31bd
|
||||
- Upgrade deps. Convert from webpack to rollup - 2c1a29c
|
||||
- Make ()[]"' as word boundaries between each other - f27b899
|
||||
- jsdiff: Replaced phantomJS by chrome - ec3114e
|
||||
- Add yarn.lock to .npmignore - 29466d8
|
||||
|
||||
Compatibility notes:
|
||||
|
||||
- Bower and Component packages no longer supported
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v3.5.0...v4.0.0)
|
||||
|
||||
## v3.5.1 - January 2026
|
||||
|
||||
Only change from 3.5.0 is a backport of the fix to https://github.com/kpdecker/jsdiff/security/advisories/GHSA-73rr-hh4g-fpgx.
|
||||
|
||||
## v3.5.0 - March 4th, 2018
|
||||
|
||||
- Omit redundant slice in join method of diffArrays - 1023590
|
||||
- Support patches with empty lines - fb0f208
|
||||
- Accept a custom JSON replacer function for JSON diffing - 69c7f0a
|
||||
- Optimize parch header parser - 2aec429
|
||||
- Fix typos - e89c832
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v3.4.0...v3.5.0)
|
||||
|
||||
## v3.4.0 - October 7th, 2017
|
||||
|
||||
- [#183](https://github.com/kpdecker/jsdiff/issues/183) - Feature request: ability to specify a custom equality checker for `diffArrays`
|
||||
- [#173](https://github.com/kpdecker/jsdiff/issues/173) - Bug: diffArrays gives wrong result on array of booleans
|
||||
- [#158](https://github.com/kpdecker/jsdiff/issues/158) - diffArrays will not compare the empty string in array?
|
||||
- comparator for custom equality checks - 30e141e
|
||||
- count oldLines and newLines when there are conflicts - 53bf384
|
||||
- Fix: diffArrays can compare falsey items - 9e24284
|
||||
- Docs: Replace grunt with npm test - 00e2f94
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v3.3.1...v3.4.0)
|
||||
|
||||
## v3.3.1 - September 3rd, 2017
|
||||
|
||||
- [#141](https://github.com/kpdecker/jsdiff/issues/141) - Cannot apply patch because my file delimiter is "/r/n" instead of "/n"
|
||||
- [#192](https://github.com/kpdecker/jsdiff/pull/192) - Fix: Bad merge when adding new files (#189)
|
||||
- correct spelling mistake - 21fa478
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v3.3.0...v3.3.1)
|
||||
|
||||
## v3.3.0 - July 5th, 2017
|
||||
|
||||
- [#114](https://github.com/kpdecker/jsdiff/issues/114) - /patch/merge not exported
|
||||
- Gracefully accept invalid newStart in hunks, same as patch(1) does. - d8a3635
|
||||
- Use regex rather than starts/ends with for parsePatch - 6cab62c
|
||||
- Add browser flag - e64f674
|
||||
- refactor: simplified code a bit more - 8f8e0f2
|
||||
- refactor: simplified code a bit - b094a6f
|
||||
- fix: some corrections re ignoreCase option - 3c78fd0
|
||||
- ignoreCase option - 3cbfbb5
|
||||
- Sanitize filename while parsing patches - 2fe8129
|
||||
- Added better installation methods - aced50b
|
||||
- Simple export of functionality - 8690f31
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v3.2.0...v3.3.0)
|
||||
|
||||
## v3.2.0 - December 26th, 2016
|
||||
|
||||
- [#156](https://github.com/kpdecker/jsdiff/pull/156) - Add `undefinedReplacement` option to `diffJson` ([@ewnd9](https://api.github.com/users/ewnd9))
|
||||
- [#154](https://github.com/kpdecker/jsdiff/pull/154) - Add `examples` and `images` to `.npmignore`. ([@wtgtybhertgeghgtwtg](https://api.github.com/users/wtgtybhertgeghgtwtg))
|
||||
- [#153](https://github.com/kpdecker/jsdiff/pull/153) - feat(structuredPatch): Pass options to diffLines ([@Kiougar](https://api.github.com/users/Kiougar))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v3.1.0...v3.2.0)
|
||||
|
||||
## v3.1.0 - November 27th, 2016
|
||||
|
||||
- [#146](https://github.com/kpdecker/jsdiff/pull/146) - JsDiff.diffArrays to compare arrays ([@wvanderdeijl](https://api.github.com/users/wvanderdeijl))
|
||||
- [#144](https://github.com/kpdecker/jsdiff/pull/144) - Split file using all possible line delimiter instead of hard-coded "/n" and join lines back using the original delimiters ([@soulbeing](https://api.github.com/users/soulbeing))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v3.0.1...v3.1.0)
|
||||
|
||||
## v3.0.1 - October 9th, 2016
|
||||
|
||||
- [#139](https://github.com/kpdecker/jsdiff/pull/139) - Make README.md look nicer in npmjs.com ([@takenspc](https://api.github.com/users/takenspc))
|
||||
- [#135](https://github.com/kpdecker/jsdiff/issues/135) - parsePatch combines patches from multiple files into a single IUniDiff when there is no "Index" line ([@ramya-rao-a](https://api.github.com/users/ramya-rao-a))
|
||||
- [#124](https://github.com/kpdecker/jsdiff/issues/124) - IE7/IE8 failure since 2.0.0 ([@boneskull](https://api.github.com/users/boneskull))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v3.0.0...v3.0.1)
|
||||
|
||||
## v3.0.0 - August 23rd, 2016
|
||||
|
||||
- [#130](https://github.com/kpdecker/jsdiff/pull/130) - Add callback argument to applyPatches `patched` option ([@piranna](https://api.github.com/users/piranna))
|
||||
- [#120](https://github.com/kpdecker/jsdiff/pull/120) - Correctly handle file names containing spaces ([@adius](https://api.github.com/users/adius))
|
||||
- [#119](https://github.com/kpdecker/jsdiff/pull/119) - Do single reflow ([@wifiextender](https://api.github.com/users/wifiextender))
|
||||
- [#117](https://github.com/kpdecker/jsdiff/pull/117) - Make more usable with long strings. ([@abnbgist](https://api.github.com/users/abnbgist))
|
||||
|
||||
Compatibility notes:
|
||||
|
||||
- applyPatches patch callback now is async and requires the callback be called to continue operation
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.3...v3.0.0)
|
||||
|
||||
## v2.2.3 - May 31st, 2016
|
||||
|
||||
- [#118](https://github.com/kpdecker/jsdiff/pull/118) - Add a fix for applying 0-length destination patches ([@chaaz](https://api.github.com/users/chaaz))
|
||||
- [#115](https://github.com/kpdecker/jsdiff/pull/115) - Fixed grammar in README ([@krizalys](https://api.github.com/users/krizalys))
|
||||
- [#113](https://github.com/kpdecker/jsdiff/pull/113) - fix typo ([@vmazare](https://api.github.com/users/vmazare))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.2...v2.2.3)
|
||||
|
||||
## v2.2.2 - March 13th, 2016
|
||||
|
||||
- [#102](https://github.com/kpdecker/jsdiff/issues/102) - diffJson with dates, returns empty curly braces ([@dr-dimitru](https://api.github.com/users/dr-dimitru))
|
||||
- [#97](https://github.com/kpdecker/jsdiff/issues/97) - Whitespaces & diffWords ([@faiwer](https://api.github.com/users/faiwer))
|
||||
- [#92](https://github.com/kpdecker/jsdiff/pull/92) - Fixes typo in the readme ([@bg451](https://api.github.com/users/bg451))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.1...v2.2.2)
|
||||
|
||||
## v2.2.1 - November 12th, 2015
|
||||
|
||||
- [#89](https://github.com/kpdecker/jsdiff/pull/89) - add in display selector to readme ([@FranDias](https://api.github.com/users/FranDias))
|
||||
- [#88](https://github.com/kpdecker/jsdiff/pull/88) - Split diffs based on file headers instead of 'Index:' metadata ([@piranna](https://api.github.com/users/piranna))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.0...v2.2.1)
|
||||
|
||||
## v2.2.0 - October 29th, 2015
|
||||
|
||||
- [#80](https://github.com/kpdecker/jsdiff/pull/80) - Fix a typo: applyPath -> applyPatch ([@fluxxu](https://api.github.com/users/fluxxu))
|
||||
- [#83](https://github.com/kpdecker/jsdiff/pull/83) - Add basic fuzzy matching to applyPatch ([@piranna](https://github.com/piranna))
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.0...v2.2.0)
|
||||
|
||||
## v2.2.0 - October 29th, 2015
|
||||
|
||||
- [#80](https://github.com/kpdecker/jsdiff/pull/80) - Fix a typo: applyPath -> applyPatch ([@fluxxu](https://api.github.com/users/fluxxu))
|
||||
- [#83](https://github.com/kpdecker/jsdiff/pull/83) - Add basic fuzzy matching to applyPatch ([@piranna](https://github.com/piranna))
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.3...v2.2.0)
|
||||
|
||||
## v2.1.3 - September 30th, 2015
|
||||
|
||||
- [#78](https://github.com/kpdecker/jsdiff/pull/78) - fix: error throwing when apply patch to empty string ([@21paradox](https://api.github.com/users/21paradox))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.2...v2.1.3)
|
||||
|
||||
## v2.1.2 - September 23rd, 2015
|
||||
|
||||
- [#76](https://github.com/kpdecker/jsdiff/issues/76) - diff headers give error ([@piranna](https://api.github.com/users/piranna))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.1...v2.1.2)
|
||||
|
||||
## v2.1.1 - September 9th, 2015
|
||||
|
||||
- [#73](https://github.com/kpdecker/jsdiff/issues/73) - Is applyPatches() exposed in the API? ([@davidparsson](https://api.github.com/users/davidparsson))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.0...v2.1.1)
|
||||
|
||||
## v2.1.0 - August 27th, 2015
|
||||
|
||||
- [#72](https://github.com/kpdecker/jsdiff/issues/72) - Consider using options object API for flag permutations ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#70](https://github.com/kpdecker/jsdiff/issues/70) - diffWords treats \n at the end as significant whitespace ([@nesQuick](https://api.github.com/users/nesQuick))
|
||||
- [#69](https://github.com/kpdecker/jsdiff/issues/69) - Missing count ([@wfalkwallace](https://api.github.com/users/wfalkwallace))
|
||||
- [#68](https://github.com/kpdecker/jsdiff/issues/68) - diffLines seems broken ([@wfalkwallace](https://api.github.com/users/wfalkwallace))
|
||||
- [#60](https://github.com/kpdecker/jsdiff/issues/60) - Support multiple diff hunks ([@piranna](https://api.github.com/users/piranna))
|
||||
- [#54](https://github.com/kpdecker/jsdiff/issues/54) - Feature Request: 3-way merge ([@mog422](https://api.github.com/users/mog422))
|
||||
- [#42](https://github.com/kpdecker/jsdiff/issues/42) - Fuzz factor for applyPatch ([@stuartpb](https://api.github.com/users/stuartpb))
|
||||
- Move whitespace ignore out of equals method - 542063c
|
||||
- Include source maps in babel output - 7f7ab21
|
||||
- Merge diff/line and diff/patch implementations - 1597705
|
||||
- Drop map utility method - 1ddc939
|
||||
- Documentation for parsePatch and applyPatches - 27c4b77
|
||||
|
||||
Compatibility notes:
|
||||
|
||||
- The undocumented ignoreWhitespace flag has been removed from the Diff equality check directly. This implementation may be copied to diff utilities if dependencies existed on this functionality.
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.0.2...v2.1.0)
|
||||
|
||||
## v2.0.2 - August 8th, 2015
|
||||
|
||||
- [#67](https://github.com/kpdecker/jsdiff/issues/67) - cannot require from npm module in node ([@commenthol](https://api.github.com/users/commenthol))
|
||||
- Convert to chai since we don’t support IE8 - a96bbad
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.0.1...v2.0.2)
|
||||
|
||||
## v2.0.1 - August 7th, 2015
|
||||
|
||||
- Add release build at proper step - 57542fd
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v2.0.0...v2.0.1)
|
||||
|
||||
## v2.0.0 - August 7th, 2015
|
||||
|
||||
- [#66](https://github.com/kpdecker/jsdiff/issues/66) - Add karma and sauce tests ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#65](https://github.com/kpdecker/jsdiff/issues/65) - Create component repository for bower ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#64](https://github.com/kpdecker/jsdiff/issues/64) - Automatically call removeEmpty for all tokenizer calls ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#62](https://github.com/kpdecker/jsdiff/pull/62) - Allow access to structured object representation of patch data ([@bittrance](https://api.github.com/users/bittrance))
|
||||
- [#61](https://github.com/kpdecker/jsdiff/pull/61) - Use svg instead of png to get better image quality ([@PeterDaveHello](https://api.github.com/users/PeterDaveHello))
|
||||
- [#29](https://github.com/kpdecker/jsdiff/issues/29) - word tokenizer works only for 7 bit ascii ([@plasmagunman](https://api.github.com/users/plasmagunman))
|
||||
|
||||
Compatibility notes:
|
||||
|
||||
- `this.removeEmpty` is now called automatically for all instances. If this is not desired, this may be overridden on a per instance basis.
|
||||
- The library has been refactored to use some ES6 features. The external APIs should remain the same, but bower projects that directly referenced the repository will now have to point to the [components/jsdiff](https://github.com/components/jsdiff) repository.
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.4.0...v2.0.0)
|
||||
|
||||
## v1.4.0 - May 6th, 2015
|
||||
|
||||
- [#57](https://github.com/kpdecker/jsdiff/issues/57) - createPatch -> applyPatch failed. ([@mog422](https://api.github.com/users/mog422))
|
||||
- [#56](https://github.com/kpdecker/jsdiff/pull/56) - Two files patch ([@rgeissert](https://api.github.com/users/rgeissert))
|
||||
- [#14](https://github.com/kpdecker/jsdiff/issues/14) - Flip added and removed order? ([@jakesandlund](https://api.github.com/users/jakesandlund))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.3.2...v1.4.0)
|
||||
|
||||
## v1.3.2 - March 30th, 2015
|
||||
|
||||
- [#53](https://github.com/kpdecker/jsdiff/pull/53) - Updated README.MD with Bower installation instructions ([@ofbriggs](https://api.github.com/users/ofbriggs))
|
||||
- [#49](https://github.com/kpdecker/jsdiff/issues/49) - Cannot read property 'oldlines' of undefined ([@nwtn](https://api.github.com/users/nwtn))
|
||||
- [#44](https://github.com/kpdecker/jsdiff/issues/44) - invalid-meta jsdiff is missing "main" entry in bower.json
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.3.1...v1.3.2)
|
||||
|
||||
## v1.3.1 - March 13th, 2015
|
||||
|
||||
- [#52](https://github.com/kpdecker/jsdiff/pull/52) - Fix for #51 Wrong result of JsDiff.diffLines ([@felicienfrancois](https://api.github.com/users/felicienfrancois))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.3.0...v1.3.1)
|
||||
|
||||
## v1.3.0 - March 2nd, 2015
|
||||
|
||||
- [#47](https://github.com/kpdecker/jsdiff/pull/47) - Adding Diff Trimmed Lines ([@JamesGould123](https://api.github.com/users/JamesGould123))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.2.2...v1.3.0)
|
||||
|
||||
## v1.2.2 - January 26th, 2015
|
||||
|
||||
- [#45](https://github.com/kpdecker/jsdiff/pull/45) - Fix AMD module loading ([@pedrocarrico](https://api.github.com/users/pedrocarrico))
|
||||
- [#43](https://github.com/kpdecker/jsdiff/pull/43) - added a bower file ([@nbrustein](https://api.github.com/users/nbrustein))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.2.1...v1.2.2)
|
||||
|
||||
## v1.2.1 - December 26th, 2014
|
||||
|
||||
- [#41](https://github.com/kpdecker/jsdiff/pull/41) - change condition of using node export system. ([@ironhee](https://api.github.com/users/ironhee))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.2.0...v1.2.1)
|
||||
|
||||
## v1.2.0 - November 29th, 2014
|
||||
|
||||
- [#37](https://github.com/kpdecker/jsdiff/pull/37) - Add support for sentences. ([@vmariano](https://api.github.com/users/vmariano))
|
||||
- [#28](https://github.com/kpdecker/jsdiff/pull/28) - Implemented diffJson ([@papandreou](https://api.github.com/users/papandreou))
|
||||
- [#27](https://github.com/kpdecker/jsdiff/issues/27) - Slow to execute over diffs with a large number of changes ([@termi](https://api.github.com/users/termi))
|
||||
- Allow for optional async diffing - 19385b9
|
||||
- Fix diffChars implementation - eaa44ed
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.1.0...v1.2.0)
|
||||
|
||||
## v1.1.0 - November 25th, 2014
|
||||
|
||||
- [#33](https://github.com/kpdecker/jsdiff/pull/33) - AMD and global exports ([@ovcharik](https://api.github.com/users/ovcharik))
|
||||
- [#32](https://github.com/kpdecker/jsdiff/pull/32) - Add support for component ([@vmariano](https://api.github.com/users/vmariano))
|
||||
- [#31](https://github.com/kpdecker/jsdiff/pull/31) - Don't rely on Array.prototype.map ([@papandreou](https://api.github.com/users/papandreou))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.0.8...v1.1.0)
|
||||
|
||||
## v1.0.8 - December 22nd, 2013
|
||||
|
||||
- [#24](https://github.com/kpdecker/jsdiff/pull/24) - Handle windows newlines on non windows machines. ([@benogle](https://api.github.com/users/benogle))
|
||||
- [#23](https://github.com/kpdecker/jsdiff/pull/23) - Prettied up the API formatting a little, and added basic node and web examples ([@airportyh](https://api.github.com/users/airportyh))
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.0.7...v1.0.8)
|
||||
|
||||
## v1.0.7 - September 11th, 2013
|
||||
|
||||
- [#22](https://github.com/kpdecker/jsdiff/pull/22) - Added variant of WordDiff that doesn't ignore whitespace differences ([@papandreou](https://api.github.com/users/papandreou)
|
||||
|
||||
- Add 0.10 to travis tests - 243a526
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.0.6...v1.0.7)
|
||||
|
||||
## v1.0.6 - August 30th, 2013
|
||||
|
||||
- [#19](https://github.com/kpdecker/jsdiff/pull/19) - Explicitly define contents of npm package ([@sindresorhus](https://api.github.com/users/sindresorhus)
|
||||
|
||||
[Commits](https://github.com/kpdecker/jsdiff/compare/v1.0.5...v1.0.6)
|
||||
Reference in new issue
Block a user