@davmash/bygone 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +32 -0
- package/bin/bygone.js +8 -1
- package/cli/commandSpec.js +11 -2
- package/cli/completions.js +9 -8
- package/cli/correspondence.js +71 -0
- package/cli/present.js +74 -20
- package/cli/tour.js +8 -1
- package/cli/tourExport.js +1 -1
- package/completions/_bygone +5 -3
- package/completions/bygone +5 -5
- package/completions/bygone.fish +2 -0
- package/docs/change-tour-format.md +60 -6
- package/docs/correspondence.md +259 -0
- package/docs/generating-change-tours.md +3 -0
- package/docs/present.md +3 -0
- package/docs/releasing.md +11 -0
- package/media/diff.worker.js +10 -9
- package/media/webview.css +1 -1
- package/media/webview.js +178 -178
- package/out/changeCorrespondence.js +739 -0
- package/out/changeTour.js +1359 -960
- package/out/gitComparison.js +4 -0
- package/out/standalone-main.js +3279 -1341
- package/out/tourCoverage.js +1852 -1448
- package/package.json +2 -2
- package/schemas/change-tour-source.schema.json +48 -3
- package/web/export-runtime.json +6 -6
- package/web/index.html +12 -0
- package/web/presenter.css +81 -0
- package/web/web-host.js +23 -20
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/changeCorrespondence.ts
|
|
21
|
+
var changeCorrespondence_exports = {};
|
|
22
|
+
__export(changeCorrespondence_exports, {
|
|
23
|
+
importChangeCorrespondence: () => importChangeCorrespondence,
|
|
24
|
+
loadChangeCorrespondence: () => loadChangeCorrespondence,
|
|
25
|
+
readCorrespondenceJson: () => readCorrespondenceJson,
|
|
26
|
+
validateChangeCorrespondence: () => validateChangeCorrespondence
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(changeCorrespondence_exports);
|
|
29
|
+
var import_crypto = require("crypto");
|
|
30
|
+
var import_child_process = require("child_process");
|
|
31
|
+
var import_fs = require("fs");
|
|
32
|
+
|
|
33
|
+
// node_modules/diff/lib/index.mjs
|
|
34
|
+
function Diff() {
|
|
35
|
+
}
|
|
36
|
+
Diff.prototype = {
|
|
37
|
+
diff: function diff(oldString, newString) {
|
|
38
|
+
var _options$timeout;
|
|
39
|
+
var options = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
|
|
40
|
+
var callback = options.callback;
|
|
41
|
+
if (typeof options === "function") {
|
|
42
|
+
callback = options;
|
|
43
|
+
options = {};
|
|
44
|
+
}
|
|
45
|
+
this.options = options;
|
|
46
|
+
var self = this;
|
|
47
|
+
function done(value) {
|
|
48
|
+
if (callback) {
|
|
49
|
+
setTimeout(function() {
|
|
50
|
+
callback(void 0, value);
|
|
51
|
+
}, 0);
|
|
52
|
+
return true;
|
|
53
|
+
} else {
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
oldString = this.castInput(oldString);
|
|
58
|
+
newString = this.castInput(newString);
|
|
59
|
+
oldString = this.removeEmpty(this.tokenize(oldString));
|
|
60
|
+
newString = this.removeEmpty(this.tokenize(newString));
|
|
61
|
+
var newLen = newString.length, oldLen = oldString.length;
|
|
62
|
+
var editLength = 1;
|
|
63
|
+
var maxEditLength = newLen + oldLen;
|
|
64
|
+
if (options.maxEditLength) {
|
|
65
|
+
maxEditLength = Math.min(maxEditLength, options.maxEditLength);
|
|
66
|
+
}
|
|
67
|
+
var maxExecutionTime = (_options$timeout = options.timeout) !== null && _options$timeout !== void 0 ? _options$timeout : Infinity;
|
|
68
|
+
var abortAfterTimestamp = Date.now() + maxExecutionTime;
|
|
69
|
+
var bestPath = [{
|
|
70
|
+
oldPos: -1,
|
|
71
|
+
lastComponent: void 0
|
|
72
|
+
}];
|
|
73
|
+
var newPos = this.extractCommon(bestPath[0], newString, oldString, 0);
|
|
74
|
+
if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
|
|
75
|
+
return done([{
|
|
76
|
+
value: this.join(newString),
|
|
77
|
+
count: newString.length
|
|
78
|
+
}]);
|
|
79
|
+
}
|
|
80
|
+
var minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
|
|
81
|
+
function execEditLength() {
|
|
82
|
+
for (var diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
|
|
83
|
+
var basePath = void 0;
|
|
84
|
+
var removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
|
|
85
|
+
if (removePath) {
|
|
86
|
+
bestPath[diagonalPath - 1] = void 0;
|
|
87
|
+
}
|
|
88
|
+
var canAdd = false;
|
|
89
|
+
if (addPath) {
|
|
90
|
+
var addPathNewPos = addPath.oldPos - diagonalPath;
|
|
91
|
+
canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
|
|
92
|
+
}
|
|
93
|
+
var canRemove = removePath && removePath.oldPos + 1 < oldLen;
|
|
94
|
+
if (!canAdd && !canRemove) {
|
|
95
|
+
bestPath[diagonalPath] = void 0;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!canRemove || canAdd && removePath.oldPos + 1 < addPath.oldPos) {
|
|
99
|
+
basePath = self.addToPath(addPath, true, void 0, 0);
|
|
100
|
+
} else {
|
|
101
|
+
basePath = self.addToPath(removePath, void 0, true, 1);
|
|
102
|
+
}
|
|
103
|
+
newPos = self.extractCommon(basePath, newString, oldString, diagonalPath);
|
|
104
|
+
if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
|
|
105
|
+
return done(buildValues(self, basePath.lastComponent, newString, oldString, self.useLongestToken));
|
|
106
|
+
} else {
|
|
107
|
+
bestPath[diagonalPath] = basePath;
|
|
108
|
+
if (basePath.oldPos + 1 >= oldLen) {
|
|
109
|
+
maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
|
|
110
|
+
}
|
|
111
|
+
if (newPos + 1 >= newLen) {
|
|
112
|
+
minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
editLength++;
|
|
117
|
+
}
|
|
118
|
+
if (callback) {
|
|
119
|
+
(function exec() {
|
|
120
|
+
setTimeout(function() {
|
|
121
|
+
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
|
|
122
|
+
return callback();
|
|
123
|
+
}
|
|
124
|
+
if (!execEditLength()) {
|
|
125
|
+
exec();
|
|
126
|
+
}
|
|
127
|
+
}, 0);
|
|
128
|
+
})();
|
|
129
|
+
} else {
|
|
130
|
+
while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
|
|
131
|
+
var ret = execEditLength();
|
|
132
|
+
if (ret) {
|
|
133
|
+
return ret;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
addToPath: function addToPath(path, added, removed, oldPosInc) {
|
|
139
|
+
var last = path.lastComponent;
|
|
140
|
+
if (last && last.added === added && last.removed === removed) {
|
|
141
|
+
return {
|
|
142
|
+
oldPos: path.oldPos + oldPosInc,
|
|
143
|
+
lastComponent: {
|
|
144
|
+
count: last.count + 1,
|
|
145
|
+
added,
|
|
146
|
+
removed,
|
|
147
|
+
previousComponent: last.previousComponent
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
} else {
|
|
151
|
+
return {
|
|
152
|
+
oldPos: path.oldPos + oldPosInc,
|
|
153
|
+
lastComponent: {
|
|
154
|
+
count: 1,
|
|
155
|
+
added,
|
|
156
|
+
removed,
|
|
157
|
+
previousComponent: last
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) {
|
|
163
|
+
var newLen = newString.length, oldLen = oldString.length, oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
|
|
164
|
+
while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) {
|
|
165
|
+
newPos++;
|
|
166
|
+
oldPos++;
|
|
167
|
+
commonCount++;
|
|
168
|
+
}
|
|
169
|
+
if (commonCount) {
|
|
170
|
+
basePath.lastComponent = {
|
|
171
|
+
count: commonCount,
|
|
172
|
+
previousComponent: basePath.lastComponent
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
basePath.oldPos = oldPos;
|
|
176
|
+
return newPos;
|
|
177
|
+
},
|
|
178
|
+
equals: function equals(left, right) {
|
|
179
|
+
if (this.options.comparator) {
|
|
180
|
+
return this.options.comparator(left, right);
|
|
181
|
+
} else {
|
|
182
|
+
return left === right || this.options.ignoreCase && left.toLowerCase() === right.toLowerCase();
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
removeEmpty: function removeEmpty(array) {
|
|
186
|
+
var ret = [];
|
|
187
|
+
for (var i = 0; i < array.length; i++) {
|
|
188
|
+
if (array[i]) {
|
|
189
|
+
ret.push(array[i]);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return ret;
|
|
193
|
+
},
|
|
194
|
+
castInput: function castInput(value) {
|
|
195
|
+
return value;
|
|
196
|
+
},
|
|
197
|
+
tokenize: function tokenize(value) {
|
|
198
|
+
return value.split("");
|
|
199
|
+
},
|
|
200
|
+
join: function join(chars) {
|
|
201
|
+
return chars.join("");
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
function buildValues(diff2, lastComponent, newString, oldString, useLongestToken) {
|
|
205
|
+
var components = [];
|
|
206
|
+
var nextComponent;
|
|
207
|
+
while (lastComponent) {
|
|
208
|
+
components.push(lastComponent);
|
|
209
|
+
nextComponent = lastComponent.previousComponent;
|
|
210
|
+
delete lastComponent.previousComponent;
|
|
211
|
+
lastComponent = nextComponent;
|
|
212
|
+
}
|
|
213
|
+
components.reverse();
|
|
214
|
+
var componentPos = 0, componentLen = components.length, newPos = 0, oldPos = 0;
|
|
215
|
+
for (; componentPos < componentLen; componentPos++) {
|
|
216
|
+
var component = components[componentPos];
|
|
217
|
+
if (!component.removed) {
|
|
218
|
+
if (!component.added && useLongestToken) {
|
|
219
|
+
var value = newString.slice(newPos, newPos + component.count);
|
|
220
|
+
value = value.map(function(value2, i) {
|
|
221
|
+
var oldValue = oldString[oldPos + i];
|
|
222
|
+
return oldValue.length > value2.length ? oldValue : value2;
|
|
223
|
+
});
|
|
224
|
+
component.value = diff2.join(value);
|
|
225
|
+
} else {
|
|
226
|
+
component.value = diff2.join(newString.slice(newPos, newPos + component.count));
|
|
227
|
+
}
|
|
228
|
+
newPos += component.count;
|
|
229
|
+
if (!component.added) {
|
|
230
|
+
oldPos += component.count;
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
component.value = diff2.join(oldString.slice(oldPos, oldPos + component.count));
|
|
234
|
+
oldPos += component.count;
|
|
235
|
+
if (componentPos && components[componentPos - 1].added) {
|
|
236
|
+
var tmp = components[componentPos - 1];
|
|
237
|
+
components[componentPos - 1] = components[componentPos];
|
|
238
|
+
components[componentPos] = tmp;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
var finalComponent = components[componentLen - 1];
|
|
243
|
+
if (componentLen > 1 && typeof finalComponent.value === "string" && (finalComponent.added || finalComponent.removed) && diff2.equals("", finalComponent.value)) {
|
|
244
|
+
components[componentLen - 2].value += finalComponent.value;
|
|
245
|
+
components.pop();
|
|
246
|
+
}
|
|
247
|
+
return components;
|
|
248
|
+
}
|
|
249
|
+
var characterDiff = new Diff();
|
|
250
|
+
var extendedWordChars = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/;
|
|
251
|
+
var reWhitespace = /\S/;
|
|
252
|
+
var wordDiff = new Diff();
|
|
253
|
+
wordDiff.equals = function(left, right) {
|
|
254
|
+
if (this.options.ignoreCase) {
|
|
255
|
+
left = left.toLowerCase();
|
|
256
|
+
right = right.toLowerCase();
|
|
257
|
+
}
|
|
258
|
+
return left === right || this.options.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right);
|
|
259
|
+
};
|
|
260
|
+
wordDiff.tokenize = function(value) {
|
|
261
|
+
var tokens = value.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/);
|
|
262
|
+
for (var i = 0; i < tokens.length - 1; i++) {
|
|
263
|
+
if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars.test(tokens[i]) && extendedWordChars.test(tokens[i + 2])) {
|
|
264
|
+
tokens[i] += tokens[i + 2];
|
|
265
|
+
tokens.splice(i + 1, 2);
|
|
266
|
+
i--;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return tokens;
|
|
270
|
+
};
|
|
271
|
+
var lineDiff = new Diff();
|
|
272
|
+
lineDiff.tokenize = function(value) {
|
|
273
|
+
if (this.options.stripTrailingCr) {
|
|
274
|
+
value = value.replace(/\r\n/g, "\n");
|
|
275
|
+
}
|
|
276
|
+
var retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
|
|
277
|
+
if (!linesAndNewlines[linesAndNewlines.length - 1]) {
|
|
278
|
+
linesAndNewlines.pop();
|
|
279
|
+
}
|
|
280
|
+
for (var i = 0; i < linesAndNewlines.length; i++) {
|
|
281
|
+
var line = linesAndNewlines[i];
|
|
282
|
+
if (i % 2 && !this.options.newlineIsToken) {
|
|
283
|
+
retLines[retLines.length - 1] += line;
|
|
284
|
+
} else {
|
|
285
|
+
if (this.options.ignoreWhitespace) {
|
|
286
|
+
line = line.trim();
|
|
287
|
+
}
|
|
288
|
+
retLines.push(line);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return retLines;
|
|
292
|
+
};
|
|
293
|
+
function diffLines(oldStr, newStr, callback) {
|
|
294
|
+
return lineDiff.diff(oldStr, newStr, callback);
|
|
295
|
+
}
|
|
296
|
+
var sentenceDiff = new Diff();
|
|
297
|
+
sentenceDiff.tokenize = function(value) {
|
|
298
|
+
return value.split(/(\S.+?[.!?])(?=\s+|$)/);
|
|
299
|
+
};
|
|
300
|
+
var cssDiff = new Diff();
|
|
301
|
+
cssDiff.tokenize = function(value) {
|
|
302
|
+
return value.split(/([{}:;,]|\s+)/);
|
|
303
|
+
};
|
|
304
|
+
function _typeof(obj) {
|
|
305
|
+
"@babel/helpers - typeof";
|
|
306
|
+
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
|
|
307
|
+
_typeof = function(obj2) {
|
|
308
|
+
return typeof obj2;
|
|
309
|
+
};
|
|
310
|
+
} else {
|
|
311
|
+
_typeof = function(obj2) {
|
|
312
|
+
return obj2 && typeof Symbol === "function" && obj2.constructor === Symbol && obj2 !== Symbol.prototype ? "symbol" : typeof obj2;
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return _typeof(obj);
|
|
316
|
+
}
|
|
317
|
+
var objectPrototypeToString = Object.prototype.toString;
|
|
318
|
+
var jsonDiff = new Diff();
|
|
319
|
+
jsonDiff.useLongestToken = true;
|
|
320
|
+
jsonDiff.tokenize = lineDiff.tokenize;
|
|
321
|
+
jsonDiff.castInput = function(value) {
|
|
322
|
+
var _this$options = this.options, undefinedReplacement = _this$options.undefinedReplacement, _this$options$stringi = _this$options.stringifyReplacer, stringifyReplacer = _this$options$stringi === void 0 ? function(k, v) {
|
|
323
|
+
return typeof v === "undefined" ? undefinedReplacement : v;
|
|
324
|
+
} : _this$options$stringi;
|
|
325
|
+
return typeof value === "string" ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, " ");
|
|
326
|
+
};
|
|
327
|
+
jsonDiff.equals = function(left, right) {
|
|
328
|
+
return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, "$1"), right.replace(/,([\r\n])/g, "$1"));
|
|
329
|
+
};
|
|
330
|
+
function canonicalize(obj, stack, replacementStack, replacer, key) {
|
|
331
|
+
stack = stack || [];
|
|
332
|
+
replacementStack = replacementStack || [];
|
|
333
|
+
if (replacer) {
|
|
334
|
+
obj = replacer(key, obj);
|
|
335
|
+
}
|
|
336
|
+
var i;
|
|
337
|
+
for (i = 0; i < stack.length; i += 1) {
|
|
338
|
+
if (stack[i] === obj) {
|
|
339
|
+
return replacementStack[i];
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
var canonicalizedObj;
|
|
343
|
+
if ("[object Array]" === objectPrototypeToString.call(obj)) {
|
|
344
|
+
stack.push(obj);
|
|
345
|
+
canonicalizedObj = new Array(obj.length);
|
|
346
|
+
replacementStack.push(canonicalizedObj);
|
|
347
|
+
for (i = 0; i < obj.length; i += 1) {
|
|
348
|
+
canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key);
|
|
349
|
+
}
|
|
350
|
+
stack.pop();
|
|
351
|
+
replacementStack.pop();
|
|
352
|
+
return canonicalizedObj;
|
|
353
|
+
}
|
|
354
|
+
if (obj && obj.toJSON) {
|
|
355
|
+
obj = obj.toJSON();
|
|
356
|
+
}
|
|
357
|
+
if (_typeof(obj) === "object" && obj !== null) {
|
|
358
|
+
stack.push(obj);
|
|
359
|
+
canonicalizedObj = {};
|
|
360
|
+
replacementStack.push(canonicalizedObj);
|
|
361
|
+
var sortedKeys = [], _key;
|
|
362
|
+
for (_key in obj) {
|
|
363
|
+
if (obj.hasOwnProperty(_key)) {
|
|
364
|
+
sortedKeys.push(_key);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
sortedKeys.sort();
|
|
368
|
+
for (i = 0; i < sortedKeys.length; i += 1) {
|
|
369
|
+
_key = sortedKeys[i];
|
|
370
|
+
canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key);
|
|
371
|
+
}
|
|
372
|
+
stack.pop();
|
|
373
|
+
replacementStack.pop();
|
|
374
|
+
} else {
|
|
375
|
+
canonicalizedObj = obj;
|
|
376
|
+
}
|
|
377
|
+
return canonicalizedObj;
|
|
378
|
+
}
|
|
379
|
+
var arrayDiff = new Diff();
|
|
380
|
+
arrayDiff.tokenize = function(value) {
|
|
381
|
+
return value.slice();
|
|
382
|
+
};
|
|
383
|
+
arrayDiff.join = arrayDiff.removeEmpty = function(value) {
|
|
384
|
+
return value;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// src/changeCorrespondence.ts
|
|
388
|
+
var MAX_BYTES = 16 * 1024 * 1024;
|
|
389
|
+
var MAX_SOURCE_BYTES = 8 * 1024 * 1024;
|
|
390
|
+
var digest = (text) => (0, import_crypto.createHash)("sha256").update(text, "utf8").digest("hex");
|
|
391
|
+
function object(value) {
|
|
392
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid correspondence object.");
|
|
393
|
+
return value;
|
|
394
|
+
}
|
|
395
|
+
function list(value, max = 2e4) {
|
|
396
|
+
if (!Array.isArray(value) || value.length > max) throw new Error("Invalid or over-budget correspondence array.");
|
|
397
|
+
return value;
|
|
398
|
+
}
|
|
399
|
+
function string(value) {
|
|
400
|
+
if (typeof value !== "string" || !value || value.length > 4096) throw new Error("Invalid correspondence string.");
|
|
401
|
+
return value;
|
|
402
|
+
}
|
|
403
|
+
function integer(value) {
|
|
404
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error("Invalid correspondence offset.");
|
|
405
|
+
return value;
|
|
406
|
+
}
|
|
407
|
+
function filePath(value) {
|
|
408
|
+
const name = string(value);
|
|
409
|
+
if (name.startsWith("/") || name.includes("\\") || name.includes("\0") || name.split("/").some((p) => !p || p === "." || p === "..")) {
|
|
410
|
+
throw new Error("Invalid correspondence repository path.");
|
|
411
|
+
}
|
|
412
|
+
return name;
|
|
413
|
+
}
|
|
414
|
+
function hash(value) {
|
|
415
|
+
const result = string(value);
|
|
416
|
+
if (!/^[0-9a-f]{64}$/.test(result)) throw new Error("Invalid correspondence digest.");
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
function status(value) {
|
|
420
|
+
if (value !== "complete" && value !== "partial" && value !== "unavailable") throw new Error("Invalid correspondence analysis status.");
|
|
421
|
+
return value;
|
|
422
|
+
}
|
|
423
|
+
function readCorrespondenceJson(file) {
|
|
424
|
+
if ((0, import_fs.statSync)(file).size > MAX_BYTES) throw new Error("Correspondence file exceeds 16 MiB.");
|
|
425
|
+
return JSON.parse((0, import_fs.readFileSync)(file, "utf8"));
|
|
426
|
+
}
|
|
427
|
+
function validateRange(value, expected) {
|
|
428
|
+
const input = object(value);
|
|
429
|
+
for (const key of ["baseOid", "headOid"]) {
|
|
430
|
+
if (input[key] !== expected[key] || !/^[0-9a-f]{40,64}$/.test(expected[key])) {
|
|
431
|
+
throw new Error("Correspondence revisions do not match the selected merge-base/head comparison.");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return { ...expected };
|
|
435
|
+
}
|
|
436
|
+
function validateChangeCorrespondence(repoRoot, input, expected) {
|
|
437
|
+
const data = object(input);
|
|
438
|
+
if (data.version !== 1) throw new Error("Unsupported correspondence version.");
|
|
439
|
+
const range = validateRange(data.range, expected);
|
|
440
|
+
const documents = object(data.documents), symbols = object(data.symbols);
|
|
441
|
+
const result = {
|
|
442
|
+
version: 1,
|
|
443
|
+
range,
|
|
444
|
+
producer: string(data.producer),
|
|
445
|
+
status: status(data.status),
|
|
446
|
+
documents: { before: [], after: [] },
|
|
447
|
+
symbols: { before: [], after: [] },
|
|
448
|
+
relations: [],
|
|
449
|
+
unresolved: [],
|
|
450
|
+
limitations: list(data.limitations, 100).map(string)
|
|
451
|
+
};
|
|
452
|
+
const entries = /* @__PURE__ */ new Map();
|
|
453
|
+
const documentsBySide = { before: /* @__PURE__ */ new Map(), after: /* @__PURE__ */ new Map() };
|
|
454
|
+
const lineIndexes = /* @__PURE__ */ new Map();
|
|
455
|
+
const lineAt = (side, path, start) => {
|
|
456
|
+
const key = `${side}:${path}`;
|
|
457
|
+
let lines = lineIndexes.get(key);
|
|
458
|
+
if (!lines) {
|
|
459
|
+
const text = documentsBySide[side].get(path);
|
|
460
|
+
lines = [0];
|
|
461
|
+
for (let at = text.indexOf("\n"); at !== -1; at = text.indexOf("\n", at + 1)) lines.push(at + 1);
|
|
462
|
+
lineIndexes.set(key, lines);
|
|
463
|
+
}
|
|
464
|
+
let low = 0, high = lines.length;
|
|
465
|
+
while (low < high) {
|
|
466
|
+
const mid = Math.floor((low + high) / 2);
|
|
467
|
+
if (lines[mid] <= start) low = mid + 1;
|
|
468
|
+
else high = mid;
|
|
469
|
+
}
|
|
470
|
+
return low;
|
|
471
|
+
};
|
|
472
|
+
let sourceBytes = 0, symbolBytes = 0;
|
|
473
|
+
for (const side of ["before", "after"]) {
|
|
474
|
+
const texts = /* @__PURE__ */ new Map();
|
|
475
|
+
const oid = side === "before" ? range.baseOid : range.headOid;
|
|
476
|
+
for (const value of list(documents[side], 128)) {
|
|
477
|
+
const doc = object(value), name = filePath(doc.path), expectedDigest = hash(doc.digest);
|
|
478
|
+
const documentStatus = string(doc.status);
|
|
479
|
+
if (!["complete", "invalid", "unsupported"].includes(documentStatus) || texts.has(name)) throw new Error("Invalid or duplicate correspondence document.");
|
|
480
|
+
const bytes = (0, import_child_process.execFileSync)("git", ["show", `${oid}:${name}`], { cwd: repoRoot, timeout: 1e4, maxBuffer: 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
|
|
481
|
+
const text = bytes.toString("utf8");
|
|
482
|
+
sourceBytes += bytes.length;
|
|
483
|
+
if (sourceBytes > MAX_SOURCE_BYTES || !Buffer.from(text, "utf8").equals(bytes) || digest(text) !== expectedDigest) {
|
|
484
|
+
throw new Error(`Correspondence source digest mismatch or source budget exceeded: ${side}/${name}`);
|
|
485
|
+
}
|
|
486
|
+
texts.set(name, { text, status: documentStatus });
|
|
487
|
+
documentsBySide[side].set(name, text);
|
|
488
|
+
result.documents[side].push({ path: name, digest: expectedDigest, status: documentStatus, text });
|
|
489
|
+
}
|
|
490
|
+
for (const value of list(symbols[side])) {
|
|
491
|
+
const entry = object(value), name = filePath(entry.path), doc = texts.get(name);
|
|
492
|
+
const id = string(entry.id), start = integer(entry.start), end = integer(entry.end);
|
|
493
|
+
if (!doc || start >= end || end > doc.text.length || entries.has(id)) throw new Error("Invalid correspondence symbol ownership/range/ID.");
|
|
494
|
+
const text = doc.text.slice(start, end);
|
|
495
|
+
symbolBytes += Buffer.byteLength(text);
|
|
496
|
+
if (symbolBytes > MAX_SOURCE_BYTES || digest(text) !== hash(entry.digest)) throw new Error(`Correspondence symbol digest mismatch or budget exceeded: ${name}`);
|
|
497
|
+
const symbol = {
|
|
498
|
+
id,
|
|
499
|
+
path: name,
|
|
500
|
+
name: string(entry.name),
|
|
501
|
+
kind: string(entry.kind),
|
|
502
|
+
start,
|
|
503
|
+
end,
|
|
504
|
+
digest: digest(text),
|
|
505
|
+
text,
|
|
506
|
+
startLine: lineAt(side, name, start),
|
|
507
|
+
endLine: lineAt(side, name, end - 1)
|
|
508
|
+
};
|
|
509
|
+
entries.set(id, { side, symbol, documentStatus: doc.status });
|
|
510
|
+
result.symbols[side].push(symbol);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const ids = /* @__PURE__ */ new Set(), sources = /* @__PURE__ */ new Set(), destinations = /* @__PURE__ */ new Set();
|
|
514
|
+
for (const value of list(data.relations)) {
|
|
515
|
+
const relation = object(value), id = string(relation.id), sourceId = string(relation.sourceId), destinationId = string(relation.destinationId);
|
|
516
|
+
const source = entries.get(sourceId), destination = entries.get(destinationId);
|
|
517
|
+
if (ids.has(id) || sources.has(sourceId) || destinations.has(destinationId)) throw new Error("Conflicting correspondence continuations; preserve them as unresolved candidates.");
|
|
518
|
+
if (source?.side !== "before" || destination?.side !== "after" || source.documentStatus !== "complete" || destination.documentStatus !== "complete") {
|
|
519
|
+
throw new Error("Correspondence endpoints must resolve to valid, parsed before/after documents.");
|
|
520
|
+
}
|
|
521
|
+
ids.add(id);
|
|
522
|
+
sources.add(sourceId);
|
|
523
|
+
destinations.add(destinationId);
|
|
524
|
+
const producers = list(relation.producers, 10).map(string);
|
|
525
|
+
if (relation.coverage !== void 0 && relation.coverage !== "complete") throw new Error("Invalid correspondence relation coverage.");
|
|
526
|
+
const covered = result.status === "partial" && relation.coverage === "complete" && producers.some((producer) => ["refactoring-miner", "bygone-strand"].includes(producer));
|
|
527
|
+
if (result.status !== "complete" && !covered && !(producers.includes("exact-declaration") && source.symbol.text === destination.symbol.text)) continue;
|
|
528
|
+
const a = source.symbol, b = destination.symbol;
|
|
529
|
+
result.relations.push({
|
|
530
|
+
id,
|
|
531
|
+
sourceId,
|
|
532
|
+
destinationId,
|
|
533
|
+
producers,
|
|
534
|
+
pathChanged: a.path !== b.path,
|
|
535
|
+
nameChanged: a.name !== b.name,
|
|
536
|
+
...relation.coverage === "complete" ? { coverage: "complete" } : {},
|
|
537
|
+
rawEqual: a.text === b.text,
|
|
538
|
+
behavior: "unassessed",
|
|
539
|
+
localDiff: (diffLines(a.text, b.text, { timeout: 100, maxEditLength: 2e3 }) || [{ value: a.text, removed: true }, { value: b.text, added: true }]).map((part) => ({
|
|
540
|
+
value: part.value,
|
|
541
|
+
...part.added ? { added: true } : {},
|
|
542
|
+
...part.removed ? { removed: true } : {}
|
|
543
|
+
}))
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
for (const value of list(data.unresolved)) {
|
|
547
|
+
const candidate = object(value);
|
|
548
|
+
const sourceIds = list(candidate.sourceIds).map(string), destinationIds = list(candidate.destinationIds).map(string);
|
|
549
|
+
if (!sourceIds.length && !destinationIds.length) throw new Error("Unresolved correspondence needs candidate endpoints.");
|
|
550
|
+
if (sourceIds.some((id) => entries.get(id)?.side !== "before") || destinationIds.some((id) => entries.get(id)?.side !== "after")) {
|
|
551
|
+
throw new Error("Unresolved correspondence references unknown endpoints.");
|
|
552
|
+
}
|
|
553
|
+
if (sourceIds.some((id) => sources.has(id)) || destinationIds.some((id) => destinations.has(id))) throw new Error("Ambiguous endpoints cannot also be accepted continuations.");
|
|
554
|
+
result.unresolved.push({ reason: string(candidate.reason), sourceIds, destinationIds });
|
|
555
|
+
}
|
|
556
|
+
if (data.diagnostics !== void 0) {
|
|
557
|
+
const diagnostics = object(data.diagnostics);
|
|
558
|
+
if (diagnostics.version !== 1) throw new Error("Unsupported correspondence diagnostics version.");
|
|
559
|
+
let locationCount = 0;
|
|
560
|
+
result.diagnostics = { version: 1, scope: list(diagnostics.scope, 20).map(string), records: list(diagnostics.records).map((value) => {
|
|
561
|
+
const record = object(value), category = string(record.category);
|
|
562
|
+
if (!["outside", "syntax", "rejected", "unmatched", "relationship", "problem"].includes(category)) throw new Error("Invalid correspondence diagnostic category.");
|
|
563
|
+
return {
|
|
564
|
+
category,
|
|
565
|
+
reason: string(record.reason),
|
|
566
|
+
...record.message !== void 0 ? { message: string(record.message) } : {},
|
|
567
|
+
...record.producerId !== void 0 ? { producerId: string(record.producerId) } : {},
|
|
568
|
+
locations: list(record.locations, 100).map((value2) => {
|
|
569
|
+
if (++locationCount > 4e4) throw new Error("Correspondence diagnostic location budget exceeded.");
|
|
570
|
+
const location = object(value2), path = filePath(location.path);
|
|
571
|
+
const side = location.side;
|
|
572
|
+
if (side !== void 0 && side !== "before" && side !== "after") throw new Error("Invalid diagnostic revision side.");
|
|
573
|
+
const symbolId = location.symbolId === void 0 ? void 0 : string(location.symbolId);
|
|
574
|
+
if (symbolId && (entries.get(symbolId)?.side !== side || entries.get(symbolId)?.symbol.path !== path)) throw new Error("Invalid diagnostic symbol reference.");
|
|
575
|
+
const start = location.start === void 0 ? symbolId ? entries.get(symbolId).symbol.start : void 0 : integer(location.start);
|
|
576
|
+
const text = side ? documentsBySide[side].get(path) : void 0;
|
|
577
|
+
if (start !== void 0 && (text === void 0 || start > text.length)) throw new Error("Invalid diagnostic source location.");
|
|
578
|
+
return {
|
|
579
|
+
path,
|
|
580
|
+
...side ? { side } : {},
|
|
581
|
+
...symbolId ? { symbolId } : {},
|
|
582
|
+
...start !== void 0 ? { start, line: lineAt(side, path, start) } : {},
|
|
583
|
+
...location.reason !== void 0 ? { reason: string(location.reason) } : {}
|
|
584
|
+
};
|
|
585
|
+
})
|
|
586
|
+
};
|
|
587
|
+
}) };
|
|
588
|
+
}
|
|
589
|
+
return result;
|
|
590
|
+
}
|
|
591
|
+
function loadChangeCorrespondence(repoRoot, file, range) {
|
|
592
|
+
return validateChangeCorrespondence(repoRoot, readCorrespondenceJson(file), range);
|
|
593
|
+
}
|
|
594
|
+
function importChangeCorrespondence(repoRoot, input, range) {
|
|
595
|
+
const analysis = object(object(input).analysis);
|
|
596
|
+
if (analysis.version !== 1) throw new Error("Unsupported producer analysis version.");
|
|
597
|
+
const inventories = object(analysis.inventories);
|
|
598
|
+
const before = object(inventories.before), after = object(inventories.after);
|
|
599
|
+
const inventoryEntries = {
|
|
600
|
+
before: new Map(list(before.entries).map(object).map((e) => [e.id, e])),
|
|
601
|
+
after: new Map(list(after.entries).map(object).map((e) => [e.id, e]))
|
|
602
|
+
};
|
|
603
|
+
const unresolved = list(analysis.unresolved).map((value) => {
|
|
604
|
+
const candidate = object(value);
|
|
605
|
+
return {
|
|
606
|
+
reason: candidate.reason || candidate.kind || "competing-origins",
|
|
607
|
+
sourceIds: candidate.sources || candidate.sourceIds || [],
|
|
608
|
+
destinationIds: candidate.destinations || candidate.destinationIds || []
|
|
609
|
+
};
|
|
610
|
+
});
|
|
611
|
+
const limitations = [
|
|
612
|
+
"Correspondence is inferred from externally supplied analysis; source checks do not prove lineage or behavior.",
|
|
613
|
+
"Symbol slices can exclude enclosing export/const/let syntax and surrounding bindings/imports; inspect complete file diffs for that context.",
|
|
614
|
+
"Analysis covers only the supplied documents and inventoried declarations; unmatched does not mean repository-wide absence.",
|
|
615
|
+
`${list(analysis.groups).length} extraction/inline groups are not accepted continuations.`,
|
|
616
|
+
`${list(analysis.rejected).length} producer candidates were rejected; ${list(analysis.issues).length} producer issues.`,
|
|
617
|
+
`${list(before.exclusions || []).length + list(after.exclusions || []).length} inventoried syntax exclusions (for example unsupported declaration forms).`,
|
|
618
|
+
`${list(analysis.unmatchedBefore).length} unmatched before symbols; ${list(analysis.unmatchedAfter).length} unmatched after symbols.`
|
|
619
|
+
];
|
|
620
|
+
const engine = object(analysis.engine);
|
|
621
|
+
if (analysis.status === "complete" && (engine.truncated === true || engine.status !== "complete")) throw new Error("Incomplete engine analysis cannot be complete.");
|
|
622
|
+
if ((engine.truncated === true || engine.status !== "complete") && list(analysis.relations).some((value) => object(value).coverage === "complete")) {
|
|
623
|
+
throw new Error("Incomplete engine analysis cannot claim complete relation coverage.");
|
|
624
|
+
}
|
|
625
|
+
const detailed = ["before", "after"].every((side) => Array.isArray(object(inventories[side]).exclusions) && list(object(inventories[side]).documents).every((doc) => Array.isArray(object(doc).diagnostics)));
|
|
626
|
+
const records = [];
|
|
627
|
+
const add = (category, raw, locations = []) => {
|
|
628
|
+
const value = object(raw);
|
|
629
|
+
records.push({
|
|
630
|
+
category,
|
|
631
|
+
reason: string(value.reason),
|
|
632
|
+
locations,
|
|
633
|
+
...value.message ? { message: string(value.message).slice(0, 4e3) } : {},
|
|
634
|
+
...value.mapperId ? { producerId: string(value.mapperId) } : {}
|
|
635
|
+
});
|
|
636
|
+
};
|
|
637
|
+
const location = (raw, side, offsets = true) => {
|
|
638
|
+
if (!raw || typeof raw !== "object") return [];
|
|
639
|
+
const value = object(raw);
|
|
640
|
+
if (!value.path) return [];
|
|
641
|
+
return [{
|
|
642
|
+
path: filePath(value.path),
|
|
643
|
+
...side ? { side } : {},
|
|
644
|
+
...offsets && Number.isSafeInteger(value.start) && value.start >= 0 ? { start: value.start } : {},
|
|
645
|
+
...value.symbolId ? { symbolId: string(value.symbolId) } : {},
|
|
646
|
+
...value.reason ? { reason: string(value.reason) } : {}
|
|
647
|
+
}];
|
|
648
|
+
};
|
|
649
|
+
if (detailed) {
|
|
650
|
+
for (const side of ["before", "after"]) {
|
|
651
|
+
const inventory = object(inventories[side]);
|
|
652
|
+
for (const exclusion of list(inventory.exclusions)) add("syntax", exclusion, location(exclusion, side));
|
|
653
|
+
for (const raw of list(inventory.documents)) {
|
|
654
|
+
const doc = object(raw);
|
|
655
|
+
for (const diagnostic of list(doc.diagnostics)) add("problem", { ...object(diagnostic), reason: "parser-diagnostic" }, location({ ...object(diagnostic), path: doc.path }, side));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
for (const raw of list(analysis.rejected)) {
|
|
659
|
+
const record = object(raw);
|
|
660
|
+
const endpoints = record.endpoints === void 0 ? [] : list(record.endpoints, 2);
|
|
661
|
+
add("rejected", record, endpoints.length ? endpoints.flatMap((endpoint) => {
|
|
662
|
+
const e = object(endpoint);
|
|
663
|
+
return location(e, e.side);
|
|
664
|
+
}) : [...location(record.source, "before", false), ...location(record.destination, "after", false)]);
|
|
665
|
+
}
|
|
666
|
+
for (const raw of list(analysis.issues)) {
|
|
667
|
+
const issue = object(raw), side = issue.side === "before" || issue.side === "after" ? issue.side : void 0;
|
|
668
|
+
const mapping = issue.mapping ? object(issue.mapping) : void 0;
|
|
669
|
+
add("problem", issue, [
|
|
670
|
+
...location(issue, side, false),
|
|
671
|
+
...location(issue.span, side, false),
|
|
672
|
+
...location(mapping?.source, "before", false),
|
|
673
|
+
...location(mapping?.destination, "after", false)
|
|
674
|
+
]);
|
|
675
|
+
}
|
|
676
|
+
for (const raw of list(analysis.groups)) {
|
|
677
|
+
const group = object(raw), locations = [];
|
|
678
|
+
for (const [side, id] of [["before", group.sourceId], ["after", group.destinationId]]) {
|
|
679
|
+
const entry = inventoryEntries[side].get(id);
|
|
680
|
+
if (entry) locations.push(...location({ ...entry, symbolId: id }, side));
|
|
681
|
+
}
|
|
682
|
+
add("relationship", { reason: group.kind, message: "Detected relationship; not an accepted continuation. Behavior unassessed." }, locations);
|
|
683
|
+
}
|
|
684
|
+
for (const raw of list(analysis.unresolved)) {
|
|
685
|
+
const group = object(raw);
|
|
686
|
+
if (!["repeated-exact-occurrences", "ambiguous-js-candidates", "ambiguous-python-candidates"].includes(String(group.reason)) || group.competingSources === void 0) continue;
|
|
687
|
+
const locations = [];
|
|
688
|
+
for (const side of ["before", "after"]) {
|
|
689
|
+
for (const id of list(side === "before" ? group.competingSources : group.competingDestinations)) {
|
|
690
|
+
const entry = inventoryEntries[side].get(id);
|
|
691
|
+
if (!entry) throw new Error("Invalid competing-origin diagnostic symbol.");
|
|
692
|
+
locations.push(...location({ ...entry, symbolId: id }, side));
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
add("unmatched", {
|
|
696
|
+
reason: group.reason,
|
|
697
|
+
message: "Competing origins; retained local continuations are included as reference points, not unresolved bindings."
|
|
698
|
+
}, locations);
|
|
699
|
+
}
|
|
700
|
+
for (const raw of list(analysis.unmatchedDiagnostics || [])) {
|
|
701
|
+
const record = object(raw), side = record.side;
|
|
702
|
+
if (side !== "before" && side !== "after") throw new Error("Invalid unmatched diagnostic side.");
|
|
703
|
+
const entry = inventoryEntries[side].get(record.symbolId);
|
|
704
|
+
if (!entry) throw new Error("Invalid unmatched diagnostic symbol.");
|
|
705
|
+
add("unmatched", record, location({ ...entry, symbolId: record.symbolId }, side));
|
|
706
|
+
}
|
|
707
|
+
if (engine.status !== "complete" || engine.truncated === true) add("problem", {
|
|
708
|
+
reason: engine.truncated === true ? "engine-truncated" : "engine-incomplete",
|
|
709
|
+
message: `Engine status: ${string(engine.status)}${engine.reason ? `; ${string(engine.reason)}` : "; no reason supplied"}.`
|
|
710
|
+
});
|
|
711
|
+
const scope = analysis.scope === void 0 ? {} : object(analysis.scope);
|
|
712
|
+
for (const raw of list(scope.excluded || [])) {
|
|
713
|
+
const record = object(raw), side = record.oid === range.baseOid ? "before" : record.oid === range.headOid ? "after" : void 0;
|
|
714
|
+
add("outside", record, location(record, side, false));
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
const producer = engine.producer === "bygone-strand" ? "Bygone Strand correspondence engine v1" : "RefactoringMiner research mapping adapter v1";
|
|
718
|
+
const artifact = {
|
|
719
|
+
version: 1,
|
|
720
|
+
range,
|
|
721
|
+
producer,
|
|
722
|
+
status: analysis.status,
|
|
723
|
+
documents: { before: before.documents, after: after.documents },
|
|
724
|
+
symbols: { before: before.entries, after: after.entries },
|
|
725
|
+
relations: analysis.relations,
|
|
726
|
+
unresolved,
|
|
727
|
+
limitations,
|
|
728
|
+
...detailed ? { diagnostics: { version: 1, scope: [String(object(analysis.scope || {}).note || "Supplied documents only; repository-wide origins were not searched.")], records } } : {}
|
|
729
|
+
};
|
|
730
|
+
return validateChangeCorrespondence(repoRoot, artifact, range);
|
|
731
|
+
}
|
|
732
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
733
|
+
0 && (module.exports = {
|
|
734
|
+
importChangeCorrespondence,
|
|
735
|
+
loadChangeCorrespondence,
|
|
736
|
+
readCorrespondenceJson,
|
|
737
|
+
validateChangeCorrespondence
|
|
738
|
+
});
|
|
739
|
+
//# sourceMappingURL=changeCorrespondence.js.map
|