@shapeshift-labs/frontier-lang-compiler 0.2.98 → 0.2.100
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/dist/declarations/semantic-edit-bundle.d.ts +90 -0
- package/dist/declarations/semantic-edit-script.d.ts +34 -37
- package/dist/declarations/semantic-lineage.d.ts +63 -34
- package/dist/declarations/semantic-patch-bundle-index.d.ts +3 -0
- package/dist/declarations/semantic-patch-bundle.d.ts +23 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/internal/index-impl/declarationRecord.js +2 -2
- package/dist/internal/index-impl/inferSemanticLineageEvents.js +8 -0
- package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +56 -64
- package/dist/internal/index-impl/replaySemanticEditProjection.js +54 -22
- package/dist/internal/index-impl/semanticEditBundleAdmission.js +220 -0
- package/dist/internal/index-impl/semanticEditBundleIndex.js +16 -10
- package/dist/internal/index-impl/semanticEditSourceRanges.js +204 -0
- package/dist/internal/index-impl/semanticHistoryLineageResolution.js +35 -1
- package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +2 -2
- package/dist/internal/index-impl/semanticLineageInferenceMatching.js +150 -13
- package/dist/internal/index-impl/semanticLineageResolutionRecords.js +28 -1
- package/dist/internal/index-impl/semanticPatchBundleAdmission.js +130 -11
- package/dist/internal/index-impl/semanticPatchBundleLineageLinks.js +199 -0
- package/dist/internal/index-impl/semanticPatchBundleOverlaps.js +6 -2
- package/dist/internal/index-impl/semanticPatchBundleRecords.js +65 -126
- package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +127 -0
- package/dist/internal/index-impl/sourceTextForSpan.js +4 -9
- package/dist/lightweight-dependency-relations.js +113 -7
- package/dist/native-import-utils.js +15 -1
- package/dist/native-region-scanner-js-helpers.js +61 -17
- package/dist/native-region-scanner-js.js +12 -4
- package/dist/semantic-import-regions.js +3 -3
- package/package.json +1 -1
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
2
|
import { idFragment, uniqueStrings } from '../../native-import-utils.js';
|
|
3
3
|
import { semanticEditIdentityFields } from './semanticEditIdentityRecords.js';
|
|
4
|
+
import {
|
|
5
|
+
insertionOffset,
|
|
6
|
+
insertionReplacement,
|
|
7
|
+
projectionCoveredContainerOperationIds,
|
|
8
|
+
removalRange,
|
|
9
|
+
scopedBodyReplacement,
|
|
10
|
+
spanOffsets
|
|
11
|
+
} from './semanticEditSourceRanges.js';
|
|
4
12
|
import { applySourceEdits, dedupeSourceEdits, validateSourceEdits } from './semanticSourceEditDedupe.js';
|
|
5
13
|
|
|
6
14
|
export function projectSemanticEditScriptToSource(input = {}) {
|
|
@@ -9,22 +17,25 @@ export function projectSemanticEditScriptToSource(input = {}) {
|
|
|
9
17
|
const headSourceText = input.headSourceText;
|
|
10
18
|
const reasonCodes = [];
|
|
11
19
|
if (!script) throw new Error('projectSemanticEditScriptToSource requires a script');
|
|
12
|
-
if (script.admission?.status !== 'auto-merge-candidate') reasonCodes.push('script-not-auto-merge-candidate');
|
|
13
20
|
if (typeof workerSourceText !== 'string') reasonCodes.push('missing-worker-source-text');
|
|
14
21
|
if (typeof headSourceText !== 'string') reasonCodes.push('missing-head-source-text');
|
|
15
22
|
const edits = [];
|
|
16
23
|
const coveredOperationIds = [];
|
|
24
|
+
const projectionCoveredOperationIds = projectionCoveredContainerOperationIds(script.operations ?? [], workerSourceText);
|
|
17
25
|
for (const [index, operation] of (script.operations ?? []).entries()) {
|
|
18
|
-
if (operation.status === 'covered') {
|
|
26
|
+
if (operation.status === 'covered' || projectionCoveredOperationIds.has(operation.id)) {
|
|
19
27
|
coveredOperationIds.push(operation.id);
|
|
20
28
|
continue;
|
|
21
29
|
}
|
|
22
|
-
const edit = sourceEditForOperation(operation, workerSourceText, headSourceText, index);
|
|
30
|
+
const edit = sourceEditForOperation(operation, workerSourceText, headSourceText, index, input.headSourcePath);
|
|
23
31
|
if (edit.ok) edits.push(edit.value);
|
|
24
32
|
else reasonCodes.push(...edit.reasonCodes);
|
|
25
33
|
}
|
|
26
34
|
const deduped = dedupeSourceEdits(edits);
|
|
27
35
|
reasonCodes.push(...validateSourceEdits(deduped.edits));
|
|
36
|
+
if (script.admission?.status !== 'auto-merge-candidate' && (reasonCodes.length > 0 || (!edits.length && !coveredOperationIds.length))) {
|
|
37
|
+
reasonCodes.push('script-not-auto-merge-candidate');
|
|
38
|
+
}
|
|
28
39
|
const blocked = reasonCodes.length > 0;
|
|
29
40
|
const sourceText = blocked ? undefined : applySourceEdits(headSourceText, deduped.edits);
|
|
30
41
|
const core = {
|
|
@@ -62,8 +73,8 @@ export function projectSemanticEditScriptToSource(input = {}) {
|
|
|
62
73
|
return { ...core, hash: hashSemanticValue(core) };
|
|
63
74
|
}
|
|
64
75
|
|
|
65
|
-
function sourceEditForOperation(operation, workerSourceText, headSourceText, order) {
|
|
66
|
-
const identity = projectionIdentity(operation);
|
|
76
|
+
function sourceEditForOperation(operation, workerSourceText, headSourceText, order, headSourcePath) {
|
|
77
|
+
const identity = projectionIdentity(operation, headSourcePath);
|
|
67
78
|
if (operation.status === 'already-applied') {
|
|
68
79
|
return { ok: true, value: { ...identity, operationId: operation.id, order, start: 0, end: 0, replacement: '', current: '', alreadyApplied: true } };
|
|
69
80
|
}
|
|
@@ -80,16 +91,23 @@ function sourceEditForOperation(operation, workerSourceText, headSourceText, ord
|
|
|
80
91
|
if (!workerOffsets) reasons.push(`worker-span-not-resolvable:${operation.id}`);
|
|
81
92
|
if (!headOffsets) reasons.push(`head-span-not-resolvable:${operation.id}`);
|
|
82
93
|
if (reasons.length) return { ok: false, reasonCodes: reasons };
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
if (operation.hashes?.workerTextHash && hashSemanticValue(
|
|
94
|
+
const anchorReplacement = workerSourceText.slice(workerOffsets.start, workerOffsets.end);
|
|
95
|
+
const anchorCurrent = headSourceText.slice(headOffsets.start, headOffsets.end);
|
|
96
|
+
if (operation.hashes?.workerTextHash && hashSemanticValue(anchorReplacement) !== operation.hashes.workerTextHash) {
|
|
86
97
|
reasons.push(`worker-span-hash-mismatch:${operation.id}`);
|
|
87
98
|
}
|
|
88
99
|
const expectedHeadHash = operation.hashes?.headTextHash ?? operation.hashes?.baseTextHash;
|
|
89
|
-
if (expectedHeadHash && hashSemanticValue(
|
|
100
|
+
if (expectedHeadHash && hashSemanticValue(anchorCurrent) !== expectedHeadHash) {
|
|
90
101
|
reasons.push(`head-span-hash-mismatch:${operation.id}`);
|
|
91
102
|
}
|
|
92
103
|
if (reasons.length) return { ok: false, reasonCodes: reasons };
|
|
104
|
+
const scoped = scopedBodyReplacement(operation, headSourceText, workerSourceText, headOffsets, workerOffsets);
|
|
105
|
+
const replacement = scoped
|
|
106
|
+
? workerSourceText.slice(scoped.worker.start, scoped.worker.end)
|
|
107
|
+
: anchorReplacement;
|
|
108
|
+
const current = scoped
|
|
109
|
+
? headSourceText.slice(scoped.head.start, scoped.head.end)
|
|
110
|
+
: anchorCurrent;
|
|
93
111
|
return {
|
|
94
112
|
ok: true,
|
|
95
113
|
value: {
|
|
@@ -97,10 +115,17 @@ function sourceEditForOperation(operation, workerSourceText, headSourceText, ord
|
|
|
97
115
|
order,
|
|
98
116
|
...identity,
|
|
99
117
|
editKind: 'replace',
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
118
|
+
sourceRangeKind: scoped?.sourceRangeKind,
|
|
119
|
+
start: scoped?.head.start ?? headOffsets.start,
|
|
120
|
+
end: scoped?.head.end ?? headOffsets.end,
|
|
121
|
+
workerStart: scoped?.worker.start ?? workerOffsets.start,
|
|
122
|
+
workerEnd: scoped?.worker.end ?? workerOffsets.end,
|
|
123
|
+
headAnchorStart: scoped ? headOffsets.start : undefined,
|
|
124
|
+
headAnchorEnd: scoped ? headOffsets.end : undefined,
|
|
125
|
+
workerAnchorStart: scoped ? workerOffsets.start : undefined,
|
|
126
|
+
workerAnchorEnd: scoped ? workerOffsets.end : undefined,
|
|
127
|
+
anchorDeletedTextHash: scoped ? hashSemanticValue(anchorCurrent) : undefined,
|
|
128
|
+
anchorReplacementTextHash: scoped ? hashSemanticValue(anchorReplacement) : undefined,
|
|
104
129
|
replacement,
|
|
105
130
|
current
|
|
106
131
|
}
|
|
@@ -165,9 +190,16 @@ function insertionEditForOperation(operation, identity, workerSourceText, headSo
|
|
|
165
190
|
};
|
|
166
191
|
}
|
|
167
192
|
|
|
168
|
-
function projectionIdentity(operation) {
|
|
193
|
+
function projectionIdentity(operation, headSourcePath) {
|
|
169
194
|
const identity = semanticEditIdentity(operation);
|
|
170
|
-
|
|
195
|
+
const sourcePath = operation.reanchor?.toSourcePath ?? headSourcePath ?? operation.insertion?.sourcePath ?? identity.sourcePath;
|
|
196
|
+
const originalSourcePath = sourcePath && identity.sourcePath && sourcePath !== identity.sourcePath
|
|
197
|
+
? identity.sourcePath
|
|
198
|
+
: identity.originalSourcePath;
|
|
199
|
+
const targetSourcePath = sourcePath && sourcePath !== identity.sourcePath
|
|
200
|
+
? sourcePath
|
|
201
|
+
: identity.targetSourcePath;
|
|
202
|
+
return { ...identity, sourcePath, originalSourcePath, targetSourcePath };
|
|
171
203
|
}
|
|
172
204
|
|
|
173
205
|
function projectionEditRecord(edit) {
|
|
@@ -197,18 +229,27 @@ function projectionEditRecord(edit) {
|
|
|
197
229
|
operationContentHash: edit.operationContentHash,
|
|
198
230
|
editContentHash: hashSemanticValue(compactRecord({
|
|
199
231
|
semanticIdentityHash: identity.semanticIdentityHash,
|
|
232
|
+
sourceRangeKind: edit.sourceRangeKind,
|
|
200
233
|
deletedTextHash,
|
|
201
234
|
replacementTextHash,
|
|
202
235
|
status: edit.alreadyApplied ? 'already-applied' : 'applied'
|
|
203
236
|
})),
|
|
237
|
+
sourceRangeKind: edit.sourceRangeKind,
|
|
204
238
|
headStart: edit.start,
|
|
205
239
|
headEnd: edit.end,
|
|
206
240
|
workerStart: edit.workerStart,
|
|
207
241
|
workerEnd: edit.workerEnd,
|
|
242
|
+
editOrder: edit.order,
|
|
243
|
+
headAnchorStart: edit.headAnchorStart,
|
|
244
|
+
headAnchorEnd: edit.headAnchorEnd,
|
|
245
|
+
workerAnchorStart: edit.workerAnchorStart,
|
|
246
|
+
workerAnchorEnd: edit.workerAnchorEnd,
|
|
208
247
|
deletedBytes: edit.current.length,
|
|
209
248
|
replacementBytes: edit.replacement.length,
|
|
210
249
|
deletedTextHash,
|
|
211
250
|
replacementTextHash,
|
|
251
|
+
anchorDeletedTextHash: edit.anchorDeletedTextHash,
|
|
252
|
+
anchorReplacementTextHash: edit.anchorReplacementTextHash,
|
|
212
253
|
replacementSpanTextHash: hashSemanticValue(edit.replacementSpanText ?? edit.replacement),
|
|
213
254
|
insertionMode: edit.insertion?.mode,
|
|
214
255
|
insertionAnchorKey: edit.insertion?.anchorKey,
|
|
@@ -247,55 +288,6 @@ function projectedSourcePath(script, edits) {
|
|
|
247
288
|
return edits.map((edit) => edit.sourcePath).find(Boolean) ?? script.sourcePath;
|
|
248
289
|
}
|
|
249
290
|
|
|
250
|
-
function spanOffsets(sourceText, span) {
|
|
251
|
-
if (typeof sourceText !== 'string' || !span) return undefined;
|
|
252
|
-
if (typeof span.start === 'number' && typeof span.end === 'number' && span.end >= span.start) return { start: span.start, end: span.end };
|
|
253
|
-
if (typeof span.startLine !== 'number') return undefined;
|
|
254
|
-
const lineStarts = [0];
|
|
255
|
-
for (let index = 0; index < sourceText.length; index += 1) if (sourceText[index] === '\n') lineStarts.push(index + 1);
|
|
256
|
-
const startLine = Math.max(1, span.startLine);
|
|
257
|
-
const endLine = Math.max(startLine, typeof span.endLine === 'number' ? span.endLine : startLine);
|
|
258
|
-
const start = lineStarts[startLine - 1];
|
|
259
|
-
const endLineStart = lineStarts[endLine - 1];
|
|
260
|
-
if (start === undefined || endLineStart === undefined) return undefined;
|
|
261
|
-
const startColumn = Math.max(1, span.startColumn ?? 1) - 1;
|
|
262
|
-
const lineEnd = lineStarts[endLine] === undefined ? sourceText.length : lineStarts[endLine] - 1;
|
|
263
|
-
const endColumn = span.endColumn === undefined ? lineEnd - endLineStart : Math.max(1, span.endColumn) - 1;
|
|
264
|
-
return { start: start + startColumn, end: endLineStart + endColumn };
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function insertionOffset(sourceText, insertion) {
|
|
268
|
-
if (typeof sourceText !== 'string') return { ok: false, reasonCodes: ['missing-head-source-text'] };
|
|
269
|
-
const mode = insertion?.mode;
|
|
270
|
-
if (mode === 'file-start') return { ok: true, offset: 0 };
|
|
271
|
-
if (mode === 'file-end') return { ok: true, offset: sourceText.length };
|
|
272
|
-
const range = spanOffsets(sourceText, insertion?.headSpan);
|
|
273
|
-
if (!range) return { ok: false, reasonCodes: ['insertion-anchor-not-resolvable'] };
|
|
274
|
-
if (mode === 'before') return { ok: true, offset: range.start };
|
|
275
|
-
if (mode === 'after') return { ok: true, offset: afterLineOffset(sourceText, range.end) };
|
|
276
|
-
return { ok: false, reasonCodes: ['insertion-mode-unsupported'] };
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function removalRange(sourceText, span) {
|
|
280
|
-
const range = { ...span };
|
|
281
|
-
if (range.end < sourceText.length && sourceText[range.end] === '\n') range.end += 1;
|
|
282
|
-
else if (range.start > 0 && sourceText[range.start - 1] === '\n') range.start -= 1;
|
|
283
|
-
return range;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function insertionReplacement(text, sourceText, offset) {
|
|
287
|
-
let replacement = String(text ?? '');
|
|
288
|
-
if (offset > 0 && sourceText[offset - 1] !== '\n') replacement = `\n${replacement}`;
|
|
289
|
-
if (offset < sourceText.length && !replacement.endsWith('\n')) replacement += '\n';
|
|
290
|
-
if (offset === sourceText.length && sourceText && !sourceText.endsWith('\n')) replacement = `\n${replacement}`;
|
|
291
|
-
if (offset === sourceText.length && !replacement.endsWith('\n')) replacement += '\n';
|
|
292
|
-
return replacement;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function afterLineOffset(sourceText, offset) {
|
|
296
|
-
return sourceText[offset] === '\n' ? offset + 1 : offset;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
291
|
function compactRecord(value) {
|
|
300
292
|
return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
|
|
301
293
|
}
|
|
@@ -3,6 +3,7 @@ import { idFragment, normalizeNativeLanguageId, uniqueStrings } from '../../nati
|
|
|
3
3
|
import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
|
|
4
4
|
import { mapDiffSymbols } from './mapDiffSymbols.js';
|
|
5
5
|
import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
|
|
6
|
+
import { afterLineOffset, bodyContentRange, spanOffsets } from './semanticEditSourceRanges.js';
|
|
6
7
|
|
|
7
8
|
export function replaySemanticEditProjection(input = {}) {
|
|
8
9
|
const projection = input.projection ?? input.semanticEditProjection;
|
|
@@ -17,7 +18,7 @@ export function replaySemanticEditProjection(input = {}) {
|
|
|
17
18
|
? currentSymbolIndex({ currentSourceText, sourcePath, language, parser: input.parser })
|
|
18
19
|
: [];
|
|
19
20
|
const edits = projection.status === 'projected' && typeof currentSourceText === 'string'
|
|
20
|
-
? (projection.edits ?? []).map((edit) => replayProjectionEdit(edit, { currentSourceText, currentSymbols }))
|
|
21
|
+
? (projection.edits ?? []).map((edit, index) => replayProjectionEdit(projectionEditWithOrder(edit, index), { currentSourceText, currentSymbols }))
|
|
21
22
|
: [];
|
|
22
23
|
const status = replayStatus(reasonCodes, edits, projection);
|
|
23
24
|
const outputSourceText = replayOutputSource(status, currentSourceText, edits);
|
|
@@ -54,14 +55,23 @@ function replayProjectionEdit(edit, context) {
|
|
|
54
55
|
if (edit.status === 'already-applied') return replayEditRecord(edit, 'already-applied', undefined, ['projection-edit-already-applied']);
|
|
55
56
|
if (typeof edit.replacementText !== 'string') return replayEditRecord(edit, 'blocked', undefined, ['missing-replacement-text']);
|
|
56
57
|
if (edit.editKind === 'insert') return replayInsertionEdit(edit, context);
|
|
57
|
-
const
|
|
58
|
-
|
|
58
|
+
const headRange = { start: edit.headStart, end: edit.headEnd };
|
|
59
|
+
const offset = checkRange(edit, headRange, context.currentSourceText, 'head-offset');
|
|
59
60
|
const symbol = findCurrentSymbol(edit, context.currentSymbols);
|
|
60
|
-
const spanRange = spanOffsets(context.currentSourceText, symbol?.sourceSpan);
|
|
61
|
-
|
|
61
|
+
const spanRange = currentSymbolEditRange(edit, spanOffsets(context.currentSourceText, symbol?.sourceSpan), context.currentSourceText);
|
|
62
|
+
if (symbol && spanRange && !sameRange(headRange, spanRange)) {
|
|
63
|
+
const moved = checkRange(edit, spanRange, context.currentSourceText, currentSymbolRangeLabel(edit));
|
|
64
|
+
if (moved) return replayEditRecord(edit, moved.status, moved.range, [moved.reason, 'offset-reanchored-by-symbol']);
|
|
65
|
+
if (edit.editKind === 'delete' && offset && rangesOverlap(headRange, spanRange)) {
|
|
66
|
+
return replayEditRecord(edit, offset.status, offset.range, [offset.reason]);
|
|
67
|
+
}
|
|
68
|
+
return replayEditRecord(edit, 'conflict', spanRange, [`${currentSymbolRangeLabel(edit)}-content-mismatch`]);
|
|
69
|
+
}
|
|
70
|
+
if (offset) return replayEditRecord(edit, offset.status, offset.range, [offset.reason]);
|
|
71
|
+
const anchored = checkRange(edit, spanRange, context.currentSourceText, currentSymbolRangeLabel(edit));
|
|
62
72
|
if (anchored) return replayEditRecord(edit, anchored.status, anchored.range, [anchored.reason, 'offset-reanchored-by-symbol']);
|
|
63
73
|
return replayEditRecord(edit, symbol ? 'conflict' : 'stale', spanRange, [
|
|
64
|
-
symbol ?
|
|
74
|
+
symbol ? `${currentSymbolRangeLabel(edit)}-content-mismatch` : 'current-symbol-anchor-missing'
|
|
65
75
|
]);
|
|
66
76
|
}
|
|
67
77
|
|
|
@@ -70,6 +80,9 @@ function replayInsertionEdit(edit, context) {
|
|
|
70
80
|
const insertedRange = spanOffsets(context.currentSourceText, inserted?.sourceSpan);
|
|
71
81
|
const already = checkRange(edit, insertedRange, context.currentSourceText, 'current-inserted-symbol');
|
|
72
82
|
if (already?.status === 'already-applied') return replayEditRecord(edit, 'already-applied', already.range, [already.reason]);
|
|
83
|
+
if (inserted && insertedRange) {
|
|
84
|
+
return replayEditRecord(edit, 'conflict', insertedRange, ['current-inserted-symbol-content-mismatch']);
|
|
85
|
+
}
|
|
73
86
|
const anchor = findInsertionAnchorSymbol(edit, context.currentSymbols);
|
|
74
87
|
const range = insertionRange(edit, anchor, context.currentSourceText);
|
|
75
88
|
if (range) return replayEditRecord(edit, 'applied', range, [anchor ? 'current-insertion-anchor' : `current-${edit.insertionMode}`]);
|
|
@@ -97,6 +110,8 @@ function replayEditRecord(edit, status, range, reasonCodes) {
|
|
|
97
110
|
sourceIdentityHash: edit.sourceIdentityHash,
|
|
98
111
|
editContentHash: edit.editContentHash,
|
|
99
112
|
editKind: edit.editKind,
|
|
113
|
+
editOrder: edit.editOrder,
|
|
114
|
+
sourceRangeKind: edit.sourceRangeKind,
|
|
100
115
|
sourcePath: edit.targetSourcePath ?? edit.sourcePath,
|
|
101
116
|
symbolName: edit.targetSymbolName ?? edit.symbolName,
|
|
102
117
|
symbolKind: edit.targetSymbolKind ?? edit.symbolKind,
|
|
@@ -143,12 +158,21 @@ function insertionRange(edit, anchor, sourceText) {
|
|
|
143
158
|
if (!anchorRange) return undefined;
|
|
144
159
|
if (edit.insertionMode === 'before') return { start: anchorRange.start, end: anchorRange.start };
|
|
145
160
|
if (edit.insertionMode === 'after') {
|
|
146
|
-
|
|
147
|
-
return { start: offset, end: offset };
|
|
161
|
+
return { start: afterLineOffset(sourceText, anchorRange.end), end: afterLineOffset(sourceText, anchorRange.end) };
|
|
148
162
|
}
|
|
149
163
|
return undefined;
|
|
150
164
|
}
|
|
151
165
|
|
|
166
|
+
function currentSymbolEditRange(edit, symbolRange, sourceText) {
|
|
167
|
+
if (!symbolRange) return undefined;
|
|
168
|
+
if (edit.sourceRangeKind === 'body-content') return bodyContentRange(sourceText, symbolRange);
|
|
169
|
+
return symbolRange;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function currentSymbolRangeLabel(edit) {
|
|
173
|
+
return edit.sourceRangeKind === 'body-content' ? 'current-symbol-body' : 'current-symbol-anchor';
|
|
174
|
+
}
|
|
175
|
+
|
|
152
176
|
function replayStatus(reasonCodes, edits, projection) {
|
|
153
177
|
if (reasonCodes.some((reason) => reason !== 'current-source-hash-mismatch')) return 'blocked';
|
|
154
178
|
if (!edits.length && !(projection.edits ?? []).length) return 'evidence-only';
|
|
@@ -189,10 +213,25 @@ function replayOutputSource(status, sourceText, edits) {
|
|
|
189
213
|
if (status === 'already-applied') return sourceText;
|
|
190
214
|
if (status !== 'accepted-clean') return undefined;
|
|
191
215
|
return edits.filter((edit) => edit.status === 'applied')
|
|
192
|
-
.sort(
|
|
216
|
+
.sort(replaySourceEditSort)
|
|
193
217
|
.reduce((text, edit) => text.slice(0, edit.start) + editReplacement(edit, edits) + text.slice(edit.end), sourceText);
|
|
194
218
|
}
|
|
195
219
|
|
|
220
|
+
function replaySourceEditSort(left, right) {
|
|
221
|
+
return right.start - left.start || right.end - left.end || (right.editOrder ?? 0) - (left.editOrder ?? 0);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function projectionEditWithOrder(edit, index) {
|
|
225
|
+
return {
|
|
226
|
+
...edit,
|
|
227
|
+
editOrder: typeof edit.editOrder === 'number'
|
|
228
|
+
? edit.editOrder
|
|
229
|
+
: typeof edit.order === 'number'
|
|
230
|
+
? edit.order
|
|
231
|
+
: index
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
196
235
|
function editReplacement(edit, edits) {
|
|
197
236
|
return edits.find((candidate) => candidate.operationId === edit.operationId)?.replacementText ?? '';
|
|
198
237
|
}
|
|
@@ -205,19 +244,12 @@ function baseReasonCodes(projection, currentSourceText) {
|
|
|
205
244
|
]);
|
|
206
245
|
}
|
|
207
246
|
|
|
208
|
-
function
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const startLine = Math.max(1, span.startLine);
|
|
215
|
-
const endLine = Math.max(startLine, typeof span.endLine === 'number' ? span.endLine : startLine);
|
|
216
|
-
const lineStart = starts[startLine - 1];
|
|
217
|
-
const endLineStart = starts[endLine - 1];
|
|
218
|
-
if (lineStart === undefined || endLineStart === undefined) return undefined;
|
|
219
|
-
const lineEnd = starts[endLine] === undefined ? sourceText.length : starts[endLine] - 1;
|
|
220
|
-
return { start: lineStart + Math.max(0, (span.startColumn ?? 1) - 1), end: endLineStart + (span.endColumn === undefined ? lineEnd - endLineStart : Math.max(0, span.endColumn - 1)) };
|
|
247
|
+
function sameRange(left, right) {
|
|
248
|
+
return left?.start === right?.start && left?.end === right?.end;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function rangesOverlap(left, right) {
|
|
252
|
+
return Boolean(left && right && left.start < right.end && right.start < left.end);
|
|
221
253
|
}
|
|
222
254
|
|
|
223
255
|
function isJavaScriptLike(language) { return language === 'javascript' || language === 'typescript'; }
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { normalizeSemanticMergeReadiness, uniqueStrings } from '../../native-import-utils.js';
|
|
2
|
+
|
|
3
|
+
export const SemanticEditBundleAdmissionStatuses = Object.freeze([
|
|
4
|
+
'none',
|
|
5
|
+
'ready',
|
|
6
|
+
'already-applied',
|
|
7
|
+
'needs-review',
|
|
8
|
+
'stale',
|
|
9
|
+
'conflict',
|
|
10
|
+
'blocked'
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export function createSemanticEditBundleAdmission(input = {}, options = {}) {
|
|
14
|
+
const scripts = array(input.semanticEditScripts ?? input.scripts ?? input.semanticEditScript);
|
|
15
|
+
const projections = array(input.semanticEditProjections ?? input.projections ?? input.semanticEditProjection);
|
|
16
|
+
const replays = array(input.semanticEditReplays ?? input.replays ?? input.semanticEditReplay);
|
|
17
|
+
const evidence = evidenceRecords(input, options);
|
|
18
|
+
const summary = summarizeSemanticEditBundle(scripts, projections, replays, evidence);
|
|
19
|
+
const computedStatus = semanticEditBundleStatus(summary);
|
|
20
|
+
const status = safeStatus(input.status ?? options.status, computedStatus, summary);
|
|
21
|
+
const readiness = normalizeSemanticMergeReadiness(input.readiness ?? options.readiness ?? readinessForStatus(status))
|
|
22
|
+
?? input.readiness ?? options.readiness ?? readinessForStatus(status);
|
|
23
|
+
const positiveAutoApplyCandidate = status === 'ready' && hasPositiveAutoMergeProof(summary);
|
|
24
|
+
const computedReviewRequired = !['ready', 'already-applied', 'none'].includes(status) || (status === 'ready' && !positiveAutoApplyCandidate);
|
|
25
|
+
return compactRecord({
|
|
26
|
+
status,
|
|
27
|
+
action: safeAction(input.action ?? options.action, status, positiveAutoApplyCandidate),
|
|
28
|
+
readiness,
|
|
29
|
+
reviewRequired: input.reviewRequired === true || computedReviewRequired,
|
|
30
|
+
autoApplyCandidate: input.autoApplyCandidate === false ? false : positiveAutoApplyCandidate,
|
|
31
|
+
autoMergeClaim: false,
|
|
32
|
+
semanticEquivalenceClaim: false,
|
|
33
|
+
reasonCodes: uniqueStrings([
|
|
34
|
+
...strings(input.reasonCodes),
|
|
35
|
+
...strings(options.reasonCodes),
|
|
36
|
+
...summary.reasonCodes,
|
|
37
|
+
...derivedReasonCodes(summary, status)
|
|
38
|
+
].filter(Boolean)),
|
|
39
|
+
sourcePaths: summary.sourcePaths,
|
|
40
|
+
scriptIds: summary.scriptIds,
|
|
41
|
+
projectionIds: summary.projectionIds,
|
|
42
|
+
replayIds: summary.replayIds,
|
|
43
|
+
summary,
|
|
44
|
+
metadata: input.metadata ?? options.metadata
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function summarizeSemanticEditBundle(scripts, projections, replays, evidence) {
|
|
49
|
+
const scriptStatusEntries = scripts.map((script) => script.admission?.status);
|
|
50
|
+
const projectionStatusEntries = projections.flatMap((projection) => [projection.status, projection.admission?.status]);
|
|
51
|
+
const replayStatusEntries = replays.map((replay) => replay.status);
|
|
52
|
+
const scriptStatuses = uniqueStrings(strings(scriptStatusEntries));
|
|
53
|
+
const projectionStatuses = uniqueStrings(strings(projectionStatusEntries));
|
|
54
|
+
const replayStatuses = uniqueStrings(strings(replayStatusEntries));
|
|
55
|
+
const replayActions = uniqueStrings(strings(replays.map((replay) => replay.admission?.action)));
|
|
56
|
+
const evidenceSummary = summarizeEvidence(evidence);
|
|
57
|
+
return {
|
|
58
|
+
scripts: scripts.length,
|
|
59
|
+
projections: projections.length,
|
|
60
|
+
replays: replays.length,
|
|
61
|
+
files: sourcePaths(scripts, projections, replays).length,
|
|
62
|
+
portableScripts: scripts.filter((script) => script.admission?.status === 'auto-merge-candidate').length,
|
|
63
|
+
portableProjections: projections.filter((projection) => projection.status === 'projected' && projection.admission?.status === 'auto-merge-candidate').length,
|
|
64
|
+
acceptedClean: replays.filter((replay) => replay.status === 'accepted-clean').length,
|
|
65
|
+
alreadyApplied: replays.filter((replay) => replay.status === 'already-applied').length,
|
|
66
|
+
conflicts: countStatuses(scriptStatusEntries, replayStatusEntries, ['conflict']),
|
|
67
|
+
stale: countStatuses(scriptStatusEntries, replayStatusEntries, ['stale']),
|
|
68
|
+
blocked: countStatuses(scriptStatusEntries, projectionStatusEntries, replayStatusEntries, ['blocked']),
|
|
69
|
+
needsReview: countStatuses(scriptStatusEntries, replayStatusEntries, ['needs-port', 'evidence-only']),
|
|
70
|
+
projected: projections.filter((projection) => projection.status === 'projected').length,
|
|
71
|
+
projectionBlocked: projections.filter((projection) => projection.status === 'blocked').length,
|
|
72
|
+
scriptStatuses,
|
|
73
|
+
projectionStatuses,
|
|
74
|
+
replayStatuses,
|
|
75
|
+
replayActions,
|
|
76
|
+
sourcePaths: sourcePaths(scripts, projections, replays),
|
|
77
|
+
scriptIds: uniqueStrings(scripts.map((script) => script.id)),
|
|
78
|
+
projectionIds: uniqueStrings(projections.map((projection) => projection.id)),
|
|
79
|
+
replayIds: uniqueStrings(replays.map((replay) => replay.id)),
|
|
80
|
+
evidenceIds: evidenceSummary.evidenceIds,
|
|
81
|
+
passedTestEvidence: evidenceSummary.passed,
|
|
82
|
+
failedTestEvidence: evidenceSummary.failed,
|
|
83
|
+
conflictEvidence: evidenceSummary.conflict,
|
|
84
|
+
staleEvidence: evidenceSummary.stale,
|
|
85
|
+
reasonCodes: uniqueStrings([
|
|
86
|
+
...scripts.flatMap((script) => strings(script.admission?.reasonCodes)),
|
|
87
|
+
...projections.flatMap((projection) => strings(projection.admission?.reasonCodes)),
|
|
88
|
+
...replays.flatMap((replay) => strings(replay.admission?.reasonCodes)),
|
|
89
|
+
...evidenceSummary.reasonCodes
|
|
90
|
+
])
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function semanticEditBundleStatus(summary) {
|
|
95
|
+
const total = summary.scripts + summary.projections + summary.replays;
|
|
96
|
+
if (summary.blocked || summary.projectionBlocked || summary.failedTestEvidence) return 'blocked';
|
|
97
|
+
if (summary.conflicts || summary.conflictEvidence) return 'conflict';
|
|
98
|
+
if (summary.stale || summary.staleEvidence) return 'stale';
|
|
99
|
+
if (total === 0) return 'none';
|
|
100
|
+
if (!summary.replays || summary.needsReview) return 'needs-review';
|
|
101
|
+
if (summary.acceptedClean === 0 && summary.alreadyApplied === summary.replays) return 'already-applied';
|
|
102
|
+
return hasPositiveAutoMergeProof(summary) ? 'ready' : 'needs-review';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function derivedReasonCodes(summary, status) {
|
|
106
|
+
return [
|
|
107
|
+
summary.scripts && !summary.projections ? 'semantic-edit-projection-missing' : undefined,
|
|
108
|
+
(summary.scripts || summary.projections) && !summary.replays ? 'semantic-edit-replay-missing' : undefined,
|
|
109
|
+
summary.scripts && summary.portableScripts !== summary.scripts ? 'semantic-edit-script-not-portable' : undefined,
|
|
110
|
+
summary.projections && summary.portableProjections !== summary.projections ? 'semantic-edit-projection-not-portable' : undefined,
|
|
111
|
+
summary.acceptedClean && !summary.passedTestEvidence ? 'semantic-edit-tests-passed-evidence-missing' : undefined,
|
|
112
|
+
summary.failedTestEvidence ? 'semantic-edit-tests-failed' : undefined,
|
|
113
|
+
summary.conflictEvidence ? 'semantic-edit-conflict-evidence' : undefined,
|
|
114
|
+
summary.staleEvidence ? 'semantic-edit-stale-evidence' : undefined,
|
|
115
|
+
status === 'ready' ? 'semantic-edit-replay-accepted-clean' : undefined,
|
|
116
|
+
status === 'ready' ? 'semantic-edit-positive-auto-merge-proof' : undefined,
|
|
117
|
+
status === 'already-applied' ? 'semantic-edit-replay-already-applied' : undefined,
|
|
118
|
+
status === 'blocked' ? 'semantic-edit-blocked' : undefined,
|
|
119
|
+
status === 'conflict' ? 'semantic-edit-conflict' : undefined,
|
|
120
|
+
status === 'stale' ? 'semantic-edit-stale' : undefined
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function hasPositiveAutoMergeProof(summary) {
|
|
125
|
+
return summary.acceptedClean > 0 &&
|
|
126
|
+
summary.acceptedClean + summary.alreadyApplied === summary.replays &&
|
|
127
|
+
summary.scripts > 0 &&
|
|
128
|
+
summary.projections > 0 &&
|
|
129
|
+
summary.portableScripts === summary.scripts &&
|
|
130
|
+
summary.portableProjections === summary.projections &&
|
|
131
|
+
summary.passedTestEvidence > 0 &&
|
|
132
|
+
summary.failedTestEvidence === 0 &&
|
|
133
|
+
summary.conflictEvidence === 0 &&
|
|
134
|
+
summary.staleEvidence === 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function readinessForStatus(status) {
|
|
138
|
+
if (['ready', 'already-applied'].includes(status)) return 'ready';
|
|
139
|
+
if (['blocked', 'conflict'].includes(status)) return 'blocked';
|
|
140
|
+
return 'needs-review';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function actionForStatus(status) {
|
|
144
|
+
if (status === 'ready') return 'admit';
|
|
145
|
+
if (status === 'already-applied') return 'skip';
|
|
146
|
+
if (status === 'none') return 'none';
|
|
147
|
+
if (status === 'stale') return 'rerun-semantic-import';
|
|
148
|
+
if (status === 'blocked' || status === 'conflict') return 'block';
|
|
149
|
+
return 'review';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function safeStatus(requested, computed, summary) {
|
|
153
|
+
if (!requested) return computed;
|
|
154
|
+
if (requested === 'ready' && !hasPositiveAutoMergeProof(summary)) return computed;
|
|
155
|
+
if (requested === 'already-applied' && computed !== 'already-applied') return computed;
|
|
156
|
+
if (['blocked', 'conflict', 'stale'].includes(requested)) return requested;
|
|
157
|
+
return computed;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function safeAction(requested, status, positiveAutoApplyCandidate) {
|
|
161
|
+
if (requested === 'admit' && !positiveAutoApplyCandidate) return actionForStatus(status);
|
|
162
|
+
if (requested === 'skip' && status !== 'already-applied') return actionForStatus(status);
|
|
163
|
+
return requested ?? actionForStatus(status);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function sourcePaths(scripts, projections, replays) {
|
|
167
|
+
return uniqueStrings(strings([
|
|
168
|
+
...scripts.map((script) => script.sourcePath),
|
|
169
|
+
...projections.map((projection) => projection.sourcePath),
|
|
170
|
+
...projections.flatMap((projection) => array(projection.edits).flatMap((edit) => [edit.sourcePath, edit.targetSourcePath])),
|
|
171
|
+
...replays.map((replay) => replay.sourcePath),
|
|
172
|
+
...replays.flatMap((replay) => array(replay.edits).map((edit) => edit.sourcePath))
|
|
173
|
+
]));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function countStatuses(...args) {
|
|
177
|
+
const statuses = args.slice(0, -1).flatMap((value) => strings(value));
|
|
178
|
+
const needles = new Set(args.at(-1));
|
|
179
|
+
return statuses.filter((status) => needles.has(status)).length;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function evidenceRecords(...sources) {
|
|
183
|
+
return sources.flatMap((source) => [
|
|
184
|
+
...array(source?.evidence),
|
|
185
|
+
...array(source?.testEvidence),
|
|
186
|
+
...array(source?.testResults),
|
|
187
|
+
...array(source?.gateEvidence),
|
|
188
|
+
...array(source?.proofEvidence)
|
|
189
|
+
]).filter(Boolean);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function summarizeEvidence(evidence) {
|
|
193
|
+
const testLike = evidence.filter(isAutoMergeTestEvidence);
|
|
194
|
+
const conflict = evidence.filter((record) => evidenceStatus(record, ['conflict', 'conflicted']) || record?.metadata?.conflict === true || strings(record?.reasonCodes ?? record?.reasons).some((reason) => reason.toLowerCase().includes('conflict')));
|
|
195
|
+
const stale = evidence.filter((record) => evidenceStatus(record, ['stale']) || record?.metadata?.stale === true || strings(record?.reasonCodes ?? record?.reasons).some((reason) => reason.toLowerCase().includes('stale')));
|
|
196
|
+
const failed = testLike.filter((record) => evidenceStatus(record, ['failed', 'failure', 'error', 'blocked', 'rejected']));
|
|
197
|
+
const passed = testLike.filter((record) => evidenceStatus(record, ['passed', 'ok', 'success', 'succeeded', 'accepted', 'verified']));
|
|
198
|
+
return {
|
|
199
|
+
evidenceIds: uniqueStrings(evidence.map((record) => record.id)),
|
|
200
|
+
passed: passed.length,
|
|
201
|
+
failed: failed.length,
|
|
202
|
+
conflict: conflict.length,
|
|
203
|
+
stale: stale.length,
|
|
204
|
+
reasonCodes: uniqueStrings(evidence.flatMap((record) => strings(record.reasonCodes ?? record.reasons)))
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function isAutoMergeTestEvidence(record) {
|
|
209
|
+
const kind = String(record?.kind ?? record?.type ?? '').toLowerCase();
|
|
210
|
+
return ['test', 'tests', 'proof', 'gate', 'verification', 'check'].includes(kind);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function evidenceStatus(record, statuses) {
|
|
214
|
+
const status = String(record?.status ?? record?.outcome ?? '').toLowerCase();
|
|
215
|
+
return statuses.includes(status);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function array(value) { if (value === undefined || value === null) return []; return Array.isArray(value) ? value : [value]; }
|
|
219
|
+
function strings(value) { return array(value).map((entry) => String(entry ?? '')).filter(Boolean); }
|
|
220
|
+
function compactRecord(value) { return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0))); }
|
|
@@ -15,16 +15,16 @@ export function semanticEditRecordIndex(scripts, projections, replays, source =
|
|
|
15
15
|
semanticEditReplayEditCount: replayEdits.length,
|
|
16
16
|
semanticEditReplayStatuses: uniqueStrings([...strings(source.semanticEditReplayStatuses), ...strings(index.semanticEditReplayStatuses), ...strings(summary.replayStatuses), ...replays.map((replay) => replay.status)]),
|
|
17
17
|
semanticEditReplayActions: uniqueStrings([...strings(source.semanticEditReplayActions), ...strings(index.semanticEditReplayActions), ...strings(summary.replayActions), ...replays.map((replay) => replay.admission?.action)]),
|
|
18
|
-
semanticEditReplayCurrentHashes: uniqueStrings([...strings(source.semanticEditReplayCurrentHashes), ...strings(index.semanticEditReplayCurrentHashes), ...replays.map((replay) => replay.currentHash)]),
|
|
19
|
-
semanticEditReplayOutputHashes: uniqueStrings([...strings(source.semanticEditReplayOutputHashes), ...strings(index.semanticEditReplayOutputHashes), ...replays.map((replay) => replay.outputHash)]),
|
|
20
|
-
semanticEditKeys: uniqueStrings([...strings(source.semanticEditKeys), ...strings(index.semanticEditKeys), ...operations.map((operation) => operation.semanticKey), ...edits.map((edit) => edit.semanticKey), ...replayEdits.map((edit) => edit.semanticKey)]),
|
|
21
|
-
semanticIdentityHashes: uniqueStrings([...strings(source.semanticIdentityHashes), ...strings(index.semanticIdentityHashes), ...operations.map((operation) => operation.semanticIdentityHash), ...edits.map((edit) => edit.semanticIdentityHash), ...replayEdits.map((edit) => edit.semanticIdentityHash)]),
|
|
22
|
-
sourceIdentityHashes: uniqueStrings([...strings(source.sourceIdentityHashes), ...strings(index.sourceIdentityHashes), ...operations.map((operation) => operation.sourceIdentityHash), ...edits.map((edit) => edit.sourceIdentityHash), ...replayEdits.map((edit) => edit.sourceIdentityHash)]),
|
|
23
|
-
operationContentHashes: uniqueStrings([...strings(source.operationContentHashes), ...strings(index.operationContentHashes), ...operations.map((operation) => operation.operationContentHash), ...edits.map((edit) => edit.operationContentHash)]),
|
|
24
|
-
editContentHashes: uniqueStrings([...strings(source.editContentHashes), ...strings(index.editContentHashes), ...edits.map((edit) => edit.editContentHash), ...replayEdits.map((edit) => edit.editContentHash)]),
|
|
25
|
-
anchorKeys: uniqueStrings([...operations.map((operation) => operation.anchor?.key), ...edits.map((edit) => edit.anchorKey)]),
|
|
26
|
-
conflictKeys: uniqueStrings([...operations.map((operation) => operation.anchor?.conflictKey), ...edits.map((edit) => edit.conflictKey)]),
|
|
27
|
-
projectedSourcePaths: uniqueStrings([...projections.map((projection) => projection.sourcePath), ...edits.flatMap((edit) => [edit.sourcePath, edit.targetSourcePath]), ...replays.map((replay) => replay.sourcePath), ...replayEdits.map((edit) => edit.sourcePath)])
|
|
18
|
+
semanticEditReplayCurrentHashes: uniqueStrings([...strings(source.semanticEditReplayCurrentHashes), ...strings(index.semanticEditReplayCurrentHashes), ...strings(summary.replayCurrentHashes), ...strings(summary.semanticEditReplayCurrentHashes), ...replays.map((replay) => replay.currentHash)]),
|
|
19
|
+
semanticEditReplayOutputHashes: uniqueStrings([...strings(source.semanticEditReplayOutputHashes), ...strings(index.semanticEditReplayOutputHashes), ...strings(summary.replayOutputHashes), ...strings(summary.semanticEditReplayOutputHashes), ...replays.map((replay) => replay.outputHash)]),
|
|
20
|
+
semanticEditKeys: uniqueStrings([...strings(source.semanticEditKeys), ...strings(index.semanticEditKeys), ...strings(summary.semanticEditKeys), ...operations.map((operation) => operation.semanticKey), ...edits.map((edit) => edit.semanticKey), ...replayEdits.map((edit) => edit.semanticKey)]),
|
|
21
|
+
semanticIdentityHashes: uniqueStrings([...strings(source.semanticIdentityHashes), ...strings(index.semanticIdentityHashes), ...strings(summary.semanticIdentityHashes), ...operations.map((operation) => operation.semanticIdentityHash), ...edits.map((edit) => edit.semanticIdentityHash), ...replayEdits.map((edit) => edit.semanticIdentityHash)]),
|
|
22
|
+
sourceIdentityHashes: uniqueStrings([...strings(source.sourceIdentityHashes), ...strings(index.sourceIdentityHashes), ...strings(summary.sourceIdentityHashes), ...operations.map((operation) => operation.sourceIdentityHash), ...edits.map((edit) => edit.sourceIdentityHash), ...replayEdits.map((edit) => edit.sourceIdentityHash)]),
|
|
23
|
+
operationContentHashes: uniqueStrings([...strings(source.operationContentHashes), ...strings(index.operationContentHashes), ...strings(summary.operationContentHashes), ...operations.map((operation) => operation.operationContentHash), ...edits.map((edit) => edit.operationContentHash), ...replayEdits.map((edit) => edit.operationContentHash)]),
|
|
24
|
+
editContentHashes: uniqueStrings([...strings(source.editContentHashes), ...strings(index.editContentHashes), ...strings(summary.editContentHashes), ...edits.map((edit) => edit.editContentHash), ...replayEdits.map((edit) => edit.editContentHash)]),
|
|
25
|
+
anchorKeys: uniqueStrings([...strings(source.anchorKeys), ...strings(index.anchorKeys), ...strings(summary.anchorKeys), ...operations.map((operation) => operation.anchor?.key), ...edits.map((edit) => edit.anchorKey), ...replayEdits.map((edit) => edit.anchorKey)]),
|
|
26
|
+
conflictKeys: uniqueStrings([...strings(source.conflictKeys), ...strings(index.conflictKeys), ...strings(summary.conflictKeys), ...operations.map((operation) => operation.anchor?.conflictKey), ...edits.map((edit) => edit.conflictKey), ...replayEdits.map((edit) => edit.conflictKey)]),
|
|
27
|
+
projectedSourcePaths: uniqueStrings([...strings(source.projectedSourcePaths), ...strings(index.projectedSourcePaths), ...strings(summary.projectedSourcePaths), ...projections.map((projection) => projection.sourcePath), ...edits.flatMap((edit) => [edit.sourcePath, edit.targetSourcePath]), ...replays.map((replay) => replay.sourcePath), ...replayEdits.map((edit) => edit.sourcePath)])
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -36,9 +36,15 @@ export function semanticEditSummary(index) {
|
|
|
36
36
|
replayIds: index.semanticEditReplayIds,
|
|
37
37
|
replayStatuses: index.semanticEditReplayStatuses,
|
|
38
38
|
replayActions: index.semanticEditReplayActions,
|
|
39
|
+
replayCurrentHashes: index.semanticEditReplayCurrentHashes,
|
|
40
|
+
replayOutputHashes: index.semanticEditReplayOutputHashes,
|
|
39
41
|
semanticEditKeys: index.semanticEditKeys,
|
|
42
|
+
semanticIdentityHashes: index.semanticIdentityHashes,
|
|
43
|
+
sourceIdentityHashes: index.sourceIdentityHashes,
|
|
40
44
|
operationContentHashes: index.operationContentHashes,
|
|
41
45
|
editContentHashes: index.editContentHashes,
|
|
46
|
+
anchorKeys: index.anchorKeys,
|
|
47
|
+
conflictKeys: index.conflictKeys,
|
|
42
48
|
projectedSourcePaths: index.projectedSourcePaths,
|
|
43
49
|
replayEditCount: index.semanticEditReplayEditCount
|
|
44
50
|
});
|