@shapeshift-labs/frontier-lang-compiler 0.2.99 → 0.2.101
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/bidirectional-target-change-evidence.d.ts +299 -0
- package/dist/declarations/bidirectional-target-change.d.ts +19 -120
- package/dist/declarations/native-project-admission.d.ts +43 -22
- package/dist/declarations/semantic-edit-bundle.d.ts +13 -1
- package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +24 -0
- package/dist/declarations/semantic-edit-script.d.ts +53 -51
- package/dist/declarations/semantic-lineage.d.ts +62 -51
- package/dist/declarations/semantic-merge-candidates.d.ts +39 -0
- package/dist/declarations/semantic-patch-bundle.d.ts +13 -0
- package/dist/declarations/semantic-sidecar-admission.d.ts +14 -0
- package/dist/declarations/semantic-sidecar.d.ts +12 -14
- package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +200 -0
- package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +62 -17
- package/dist/internal/index-impl/createNativeSourcePreservation.js +16 -1
- package/dist/internal/index-impl/createProjectImportAdmissionRecord.js +151 -1
- package/dist/internal/index-impl/createSemanticImportSidecar.js +5 -0
- package/dist/internal/index-impl/createSemanticImportSidecarAdmission.js +29 -11
- package/dist/internal/index-impl/declarationRecord.js +2 -2
- package/dist/internal/index-impl/inferSemanticLineageEvents.js +8 -0
- package/dist/internal/index-impl/nativeChangeProjectionEndpoint.js +56 -16
- package/dist/internal/index-impl/projectImportAdmissionMergeScore.js +26 -74
- package/dist/internal/index-impl/projectImportAdmissionProjectionCoverage.js +74 -0
- package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +92 -74
- package/dist/internal/index-impl/replaySemanticEditProjection.js +114 -40
- package/dist/internal/index-impl/semanticEditBundleAdmission.js +95 -12
- package/dist/internal/index-impl/semanticEditBundleIndex.js +16 -10
- package/dist/internal/index-impl/semanticEditInsertionAnchors.js +8 -5
- package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +167 -0
- package/dist/internal/index-impl/semanticEditSourceRanges.js +283 -0
- package/dist/internal/index-impl/semanticHistoryLineageResolution.js +56 -3
- package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +2 -2
- package/dist/internal/index-impl/semanticLineageHashEvidence.js +97 -0
- package/dist/internal/index-impl/semanticLineageInferenceMatching.js +158 -13
- package/dist/internal/index-impl/semanticLineageResolutionRecords.js +46 -2
- package/dist/internal/index-impl/semanticMergeCandidateRecords.js +22 -2
- package/dist/internal/index-impl/semanticMergeCandidateScoreFacets.js +221 -0
- package/dist/internal/index-impl/semanticPatchBundleAdmission.js +122 -20
- package/dist/internal/index-impl/semanticPatchBundleLineageLinks.js +199 -0
- package/dist/internal/index-impl/semanticPatchBundleOverlaps.js +29 -3
- package/dist/internal/index-impl/semanticPatchBundleRecords.js +28 -104
- package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +127 -0
- package/dist/internal/index-impl/sourcePreservationFromProjectionContext.js +9 -2
- package/dist/internal/index-impl/sourceTextForSpan.js +4 -9
- package/dist/lightweight-dependency-relations.js +113 -7
- package/dist/native-import-language-profiles.js +10 -2
- package/dist/native-import-utils.js +15 -1
- package/dist/native-region-scanner-js-helpers.js +68 -18
- package/dist/native-region-scanner-js-imports.js +7 -0
- package/dist/native-region-scanner-js.js +16 -8
- package/dist/native-region-scanner.js +2 -1
- package/dist/semantic-import-regions.js +8 -6
- package/dist/semantic-import-sidecar-admission-types.d.ts +14 -0
- package/dist/semantic-import-sidecar-entry.js +151 -7
- package/dist/semantic-import-sidecar-types.d.ts +18 -13
- package/dist/semantic-import-source-preservation-utils.js +55 -0
- package/dist/semantic-import-source-preservation.js +98 -3
- package/package.json +1 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
|
|
3
|
+
export function projectionCoveredContainerOperationIds(operations, workerSourceText) {
|
|
4
|
+
if (typeof workerSourceText !== 'string') return new Set();
|
|
5
|
+
const result = new Set();
|
|
6
|
+
for (const operation of operations ?? []) {
|
|
7
|
+
if (!isProjectionCoverableContainer(operation)) continue;
|
|
8
|
+
if (workerContainerCoveredByInsertedChildren(operation, operations, workerSourceText)) result.add(operation.id);
|
|
9
|
+
}
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function spanOffsets(sourceText, span) {
|
|
14
|
+
if (typeof sourceText !== 'string' || !span) return undefined;
|
|
15
|
+
if (typeof span.start === 'number' && typeof span.end === 'number' && span.end >= span.start) {
|
|
16
|
+
return { start: span.start, end: span.end };
|
|
17
|
+
}
|
|
18
|
+
if (typeof span.startLine !== 'number') return undefined;
|
|
19
|
+
const lineStarts = [0];
|
|
20
|
+
for (let index = 0; index < sourceText.length; index += 1) if (sourceText[index] === '\n') lineStarts.push(index + 1);
|
|
21
|
+
const startLine = Math.max(1, span.startLine);
|
|
22
|
+
const endLine = Math.max(startLine, typeof span.endLine === 'number' ? span.endLine : startLine);
|
|
23
|
+
const start = lineStarts[startLine - 1];
|
|
24
|
+
const endLineStart = lineStarts[endLine - 1];
|
|
25
|
+
if (start === undefined || endLineStart === undefined) return undefined;
|
|
26
|
+
const startColumn = Math.max(1, span.startColumn ?? 1) - 1;
|
|
27
|
+
const lineEnd = lineContentEndOffset(sourceText, lineStarts[endLine]);
|
|
28
|
+
const endColumn = span.endColumn === undefined ? lineEnd - endLineStart : Math.max(1, span.endColumn) - 1;
|
|
29
|
+
return { start: start + startColumn, end: endLineStart + endColumn };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function scopedBodyReplacement(operation, headSourceText, workerSourceText, headOffsets, workerOffsets) {
|
|
33
|
+
if (!isBodyReplacement(operation)) return undefined;
|
|
34
|
+
const head = bodyContentRange(headSourceText, headOffsets);
|
|
35
|
+
const worker = bodyContentRange(workerSourceText, workerOffsets);
|
|
36
|
+
if (!head || !worker) return undefined;
|
|
37
|
+
const headPrefix = headSourceText.slice(headOffsets.start, head.start);
|
|
38
|
+
const workerPrefix = workerSourceText.slice(workerOffsets.start, worker.start);
|
|
39
|
+
const headSuffix = headSourceText.slice(head.end, headOffsets.end);
|
|
40
|
+
const workerSuffix = workerSourceText.slice(worker.end, workerOffsets.end);
|
|
41
|
+
if (headPrefix !== workerPrefix || headSuffix !== workerSuffix) return undefined;
|
|
42
|
+
return { sourceRangeKind: 'body-content', head, worker };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function bodyContentRange(sourceText, range) {
|
|
46
|
+
const pairs = bracePairs(sourceText, range);
|
|
47
|
+
const close = trailingBodyCloseOffset(sourceText, range);
|
|
48
|
+
const pair = close === undefined ? undefined : pairs.find((candidate) => candidate.close === close);
|
|
49
|
+
return pair ? { start: pair.open + 1, end: pair.close } : undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function insertionOffset(sourceText, insertion, context = {}) {
|
|
53
|
+
if (typeof sourceText !== 'string') return { ok: false, reasonCodes: ['missing-head-source-text'] };
|
|
54
|
+
const mode = insertion?.mode;
|
|
55
|
+
if (mode === 'file-start') return { ok: true, offset: 0 };
|
|
56
|
+
if (mode === 'file-end') return { ok: true, offset: sourceText.length };
|
|
57
|
+
const resolved = insertionAnchorResolution(sourceText, insertion, context);
|
|
58
|
+
if (!resolved?.range) return { ok: false, reasonCodes: ['insertion-anchor-not-resolvable'] };
|
|
59
|
+
if (resolved.mode === 'before') return { ok: true, offset: resolved.range.start };
|
|
60
|
+
if (resolved.mode === 'after') return { ok: true, offset: afterLineOffset(sourceText, resolved.range.end) };
|
|
61
|
+
return { ok: false, reasonCodes: ['insertion-mode-unsupported'] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function removalRange(sourceText, span) {
|
|
65
|
+
const range = { ...span };
|
|
66
|
+
const next = lineBreakEndOffset(sourceText, range.end);
|
|
67
|
+
if (next !== range.end) range.end = next;
|
|
68
|
+
else {
|
|
69
|
+
const previous = previousLineBreakStartOffset(sourceText, range.start);
|
|
70
|
+
if (previous !== range.start) range.start = previous;
|
|
71
|
+
}
|
|
72
|
+
return range;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function insertionReplacement(text, sourceText, offset) {
|
|
76
|
+
let replacement = String(text ?? '');
|
|
77
|
+
const newline = sourceLineEnding(sourceText);
|
|
78
|
+
if (offset > 0 && !isLineBreak(sourceText[offset - 1])) replacement = `${newline}${replacement}`;
|
|
79
|
+
if (offset < sourceText.length && !endsWithLineBreak(replacement)) replacement += newline;
|
|
80
|
+
if (offset === sourceText.length && sourceText && !endsWithLineBreak(sourceText)) replacement = `${newline}${replacement}`;
|
|
81
|
+
if (offset === sourceText.length && !endsWithLineBreak(replacement)) replacement += newline;
|
|
82
|
+
return replacement;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function afterLineOffset(sourceText, offset) {
|
|
86
|
+
return lineBreakEndOffset(sourceText, offset);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isProjectionCoverableContainer(operation) {
|
|
90
|
+
if (['portable', 'already-applied', 'covered'].includes(operation.status)) return false;
|
|
91
|
+
if (operation.changeKind !== 'modified') return false;
|
|
92
|
+
if (!operation.spans?.worker || !operation.hashes?.baseTextHash) return false;
|
|
93
|
+
const kind = String(operation.anchor?.regionKind ?? operation.regionKind ?? '');
|
|
94
|
+
return kind === 'type' || kind === 'config' || kind === 'content' || kind === 'route' || kind === 'property';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function workerContainerCoveredByInsertedChildren(container, operations, workerSourceText) {
|
|
98
|
+
const containerWorker = spanOffsets(workerSourceText, container.spans?.worker);
|
|
99
|
+
if (!containerWorker) return false;
|
|
100
|
+
const childRanges = (operations ?? [])
|
|
101
|
+
.filter((operation) => operation.id !== container.id)
|
|
102
|
+
.filter((operation) => operation.changeKind === 'added' || String(operation.kind ?? '').startsWith('add'))
|
|
103
|
+
.filter((operation) => ['portable', 'already-applied'].includes(operation.status))
|
|
104
|
+
.map((operation) => spanOffsets(workerSourceText, operation.spans?.worker))
|
|
105
|
+
.filter((range) => containedRange(range, containerWorker))
|
|
106
|
+
.map((range) => insertionRemovalRange(workerSourceText, range, containerWorker));
|
|
107
|
+
if (!childRanges.length) return false;
|
|
108
|
+
const stripped = childRanges
|
|
109
|
+
.sort((left, right) => right.start - left.start || right.end - left.end)
|
|
110
|
+
.reduce((text, range) => text.slice(0, range.start - containerWorker.start) + text.slice(range.end - containerWorker.start), workerSourceText.slice(containerWorker.start, containerWorker.end));
|
|
111
|
+
return hashSemanticValue(stripped) === container.hashes.baseTextHash;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function containedRange(inner, outer) {
|
|
115
|
+
return Boolean(inner && outer && outer.start <= inner.start && inner.end <= outer.end);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function insertionRemovalRange(sourceText, span, container) {
|
|
119
|
+
const range = { ...span };
|
|
120
|
+
const next = lineBreakEndOffset(sourceText, range.end);
|
|
121
|
+
if (next !== range.end && next <= container.end) range.end = next;
|
|
122
|
+
else {
|
|
123
|
+
const previous = previousLineBreakStartOffset(sourceText, range.start);
|
|
124
|
+
if (previous !== range.start && previous >= container.start) range.start = previous;
|
|
125
|
+
}
|
|
126
|
+
return range;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function lineContentEndOffset(sourceText, nextLineStart) {
|
|
130
|
+
if (nextLineStart === undefined) return sourceText.length;
|
|
131
|
+
const lineBreakStart = sourceText[nextLineStart - 2] === '\r' ? nextLineStart - 2 : nextLineStart - 1;
|
|
132
|
+
return Math.max(0, lineBreakStart);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function lineBreakEndOffset(sourceText, offset) {
|
|
136
|
+
if (sourceText[offset] === '\r' && sourceText[offset + 1] === '\n') return offset + 2;
|
|
137
|
+
if (isLineBreak(sourceText[offset])) return offset + 1;
|
|
138
|
+
return offset;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function previousLineBreakStartOffset(sourceText, offset) {
|
|
142
|
+
if (sourceText[offset - 1] === '\n') return sourceText[offset - 2] === '\r' ? offset - 2 : offset - 1;
|
|
143
|
+
if (sourceText[offset - 1] === '\r') return offset - 1;
|
|
144
|
+
return offset;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function sourceLineEnding(sourceText) {
|
|
148
|
+
if (sourceText.includes('\r\n')) return '\r\n';
|
|
149
|
+
return sourceText.includes('\r') ? '\r' : '\n';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function endsWithLineBreak(value) {
|
|
153
|
+
return isLineBreak(value[value.length - 1]);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isLineBreak(char) {
|
|
157
|
+
return char === '\n' || char === '\r';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isBodyReplacement(operation) {
|
|
161
|
+
return operation.changeKind === 'modified' && (operation.kind === 'replaceBody' || operation.anchor?.regionKind === 'body');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function insertionAnchorResolution(sourceText, insertion, context) {
|
|
165
|
+
const candidates = insertionAnchorCandidates(insertion);
|
|
166
|
+
for (const candidate of candidates) {
|
|
167
|
+
const symbol = insertionAnchorSymbol(candidate, context.symbols);
|
|
168
|
+
const range = spanOffsets(sourceText, symbol?.sourceSpan);
|
|
169
|
+
if (range) return { mode: candidate.mode, range };
|
|
170
|
+
}
|
|
171
|
+
for (const candidate of candidates) {
|
|
172
|
+
const range = spanOffsets(sourceText, candidate.headSpan);
|
|
173
|
+
if (range) return { mode: candidate.mode, range };
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function insertionAnchorCandidates(insertion) {
|
|
179
|
+
const seen = new Set();
|
|
180
|
+
const result = [];
|
|
181
|
+
for (const candidate of [insertion, ...(Array.isArray(insertion?.anchorCandidates) ? insertion.anchorCandidates : [])]) {
|
|
182
|
+
if (!candidate || (candidate.mode !== 'before' && candidate.mode !== 'after')) continue;
|
|
183
|
+
const key = [candidate.mode, candidate.anchorKey, candidate.anchorSymbolId, candidate.anchorSymbolName, candidate.anchorSymbolKind].join('\0');
|
|
184
|
+
if (seen.has(key)) continue;
|
|
185
|
+
seen.add(key);
|
|
186
|
+
result.push(candidate);
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function insertionAnchorSymbol(candidate, symbols) {
|
|
192
|
+
const symbolList = Array.isArray(symbols)
|
|
193
|
+
? symbols
|
|
194
|
+
: symbols?.values
|
|
195
|
+
? [...symbols.values()]
|
|
196
|
+
: [];
|
|
197
|
+
const keys = [candidate.anchorKey, candidate.anchorSymbolId].filter(Boolean);
|
|
198
|
+
const exact = symbolList.find((symbol) => [symbol.ownershipKey, symbol.key, symbol.id].some((key) => key && keys.includes(key)));
|
|
199
|
+
if (exact) return exact;
|
|
200
|
+
return symbolList.find((symbol) => symbol.name === candidate.anchorSymbolName && (!candidate.anchorSymbolKind || symbol.kind === candidate.anchorSymbolKind));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function trailingBodyCloseOffset(sourceText, range) {
|
|
204
|
+
if (typeof sourceText !== 'string' || !range) return undefined;
|
|
205
|
+
let index = range.end - 1;
|
|
206
|
+
index = previousCodeOffset(sourceText, index, range.start);
|
|
207
|
+
if (sourceText[index] === ';' || sourceText[index] === ',') {
|
|
208
|
+
index -= 1;
|
|
209
|
+
index = previousCodeOffset(sourceText, index, range.start);
|
|
210
|
+
}
|
|
211
|
+
return sourceText[index] === '}' ? index : undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function previousCodeOffset(sourceText, index, minIndex) {
|
|
215
|
+
let cursor = index;
|
|
216
|
+
while (cursor >= minIndex) {
|
|
217
|
+
while (cursor >= minIndex && /\s/.test(sourceText[cursor])) cursor -= 1;
|
|
218
|
+
const blockStart = sourceText.lastIndexOf('/*', cursor);
|
|
219
|
+
if (sourceText[cursor] === '/' && sourceText[cursor - 1] === '*' && blockStart >= minIndex) {
|
|
220
|
+
cursor = blockStart - 1;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const lineStart = Math.max(minIndex, sourceText.lastIndexOf('\n', cursor) + 1);
|
|
224
|
+
const lineComment = sourceText.lastIndexOf('//', cursor);
|
|
225
|
+
if (lineComment >= lineStart) {
|
|
226
|
+
cursor = lineComment - 1;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
return cursor;
|
|
230
|
+
}
|
|
231
|
+
return cursor;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function bracePairs(sourceText, range) {
|
|
235
|
+
if (typeof sourceText !== 'string' || !range || range.end <= range.start) return [];
|
|
236
|
+
const stack = [];
|
|
237
|
+
const pairs = [];
|
|
238
|
+
let quote;
|
|
239
|
+
let escaped = false;
|
|
240
|
+
let lineComment = false;
|
|
241
|
+
let blockComment = false;
|
|
242
|
+
for (let index = range.start; index < range.end; index += 1) {
|
|
243
|
+
const char = sourceText[index];
|
|
244
|
+
const next = sourceText[index + 1];
|
|
245
|
+
if (lineComment) {
|
|
246
|
+
if (char === '\n' || char === '\r') lineComment = false;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (blockComment) {
|
|
250
|
+
if (char === '*' && next === '/') {
|
|
251
|
+
blockComment = false;
|
|
252
|
+
index += 1;
|
|
253
|
+
}
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (quote) {
|
|
257
|
+
if (escaped) escaped = false;
|
|
258
|
+
else if (char === '\\') escaped = true;
|
|
259
|
+
else if (char === quote) quote = undefined;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (char === '/' && next === '/') {
|
|
263
|
+
lineComment = true;
|
|
264
|
+
index += 1;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (char === '/' && next === '*') {
|
|
268
|
+
blockComment = true;
|
|
269
|
+
index += 1;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (char === '\'' || char === '"' || char === '`') {
|
|
273
|
+
quote = char;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (char === '{') stack.push(index);
|
|
277
|
+
else if (char === '}') {
|
|
278
|
+
const open = stack.pop();
|
|
279
|
+
if (open !== undefined) pairs.push({ open, close: index });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return pairs;
|
|
283
|
+
}
|
|
@@ -22,7 +22,11 @@ export function resolveSemanticHistoryRecordLineage(record = {}, eventsOrMap = [
|
|
|
22
22
|
semanticAnchorKeys: resolveIndexKeys(sourceIndex.semanticAnchorKeys, byAnchorKey, options),
|
|
23
23
|
sourcePaths: uniqueStrings([
|
|
24
24
|
...array(sourceIndex.sourcePaths),
|
|
25
|
-
...resolutions.flatMap((resolution) => resolution
|
|
25
|
+
...resolutions.flatMap((resolution) => resolutionSourcePaths(resolution))
|
|
26
|
+
]),
|
|
27
|
+
lineageResolutionIds: uniqueStrings([
|
|
28
|
+
...array(sourceIndex.lineageResolutionIds),
|
|
29
|
+
...resolutions.map((resolution) => resolution.id)
|
|
26
30
|
]),
|
|
27
31
|
lineageEventIds: uniqueStrings([
|
|
28
32
|
...array(sourceIndex.lineageEventIds),
|
|
@@ -105,6 +109,13 @@ function createResolvedHistoryRecord(record, index, resolution) {
|
|
|
105
109
|
inactiveAnchorKeys: resolution.summary.inactiveAnchorKeys,
|
|
106
110
|
blockedAnchorKeys: resolution.summary.blockedAnchorKeys
|
|
107
111
|
},
|
|
112
|
+
lineageResolutionIds: resolution.summary.lineageResolutionIds,
|
|
113
|
+
lineageEventIds: resolution.summary.traversedEventIds,
|
|
114
|
+
terminalEventIds: resolution.summary.terminalEventIds,
|
|
115
|
+
sourcePaths: resolution.summary.sourcePaths,
|
|
116
|
+
evidenceIds: resolution.summary.evidenceIds,
|
|
117
|
+
proofIds: resolution.summary.proofIds,
|
|
118
|
+
reasonCodes: resolution.summary.reasonCodes,
|
|
108
119
|
autoMergeClaim: false,
|
|
109
120
|
semanticEquivalenceClaim: false
|
|
110
121
|
}
|
|
@@ -126,12 +137,17 @@ function resolveIndexKeys(values, resolutions, options) {
|
|
|
126
137
|
const key = String(value);
|
|
127
138
|
const resolution = resolutions.get(key);
|
|
128
139
|
if (!resolution) return [key];
|
|
140
|
+
const current = resolution.currentAnchors.map((anchor) => anchor.key).filter(Boolean);
|
|
129
141
|
if (resolution.status === 'deleted' && options.keepDeletedAnchors !== true) return [];
|
|
130
142
|
if (resolution.status === 'cycle' && options.keepBlockedAnchors !== true) return [];
|
|
131
143
|
if (resolution.status === 'max-depth' && options.keepBlockedAnchors !== true) return [];
|
|
132
144
|
if (resolution.status === 'not-found' && options.keepUnresolvedAnchors !== true) return [];
|
|
145
|
+
if (resolution.status === 'ambiguous' && resolutionHasInactiveTerminal(resolution)) {
|
|
146
|
+
if (options.keepCandidateAnchors === true) return current.length ? current : [key];
|
|
147
|
+
if (options.keepDeletedAnchors === true || options.keepInactiveAnchors === true) return [key];
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
133
150
|
if (resolution.status === 'ambiguous' && options.keepCandidateAnchors === false) return [];
|
|
134
|
-
const current = resolution.currentAnchors.map((anchor) => anchor.key).filter(Boolean);
|
|
135
151
|
return current.length ? current : [key];
|
|
136
152
|
}));
|
|
137
153
|
}
|
|
@@ -159,8 +175,11 @@ function summarizeSemanticHistoryLineageResolutions(resolutions, index, anchorIn
|
|
|
159
175
|
deletedAnchorKeys: uniqueStrings(anchorInventory.deleted.map((anchor) => anchor.key)),
|
|
160
176
|
unresolvedAnchorKeys: uniqueStrings(anchorInventory.unresolved.map((anchor) => anchor.key)),
|
|
161
177
|
blockedAnchorKeys: uniqueStrings(anchorInventory.blocked.map((anchor) => anchor.key)),
|
|
178
|
+
lineageResolutionIds: uniqueStrings(resolutions.map((resolution) => resolution.id)),
|
|
162
179
|
traversedEventIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.traversedEventIds)),
|
|
163
180
|
terminalEventIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.terminalEventIds)),
|
|
181
|
+
evidenceIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.evidenceIds)),
|
|
182
|
+
proofIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.proofIds)),
|
|
164
183
|
reasonCodes: uniqueStrings(resolutions.flatMap((resolution) => resolution.reasonCodes))
|
|
165
184
|
};
|
|
166
185
|
}
|
|
@@ -179,7 +198,10 @@ function createAnchorInventory(resolutions) {
|
|
|
179
198
|
const current = resolution.currentAnchors.map((anchor) => anchorEntry(anchor, resolution));
|
|
180
199
|
if (resolution.status === 'ambiguous') {
|
|
181
200
|
inventory.candidate.push(...current);
|
|
182
|
-
if (start)
|
|
201
|
+
if (start) {
|
|
202
|
+
inventory.inactive.push(start);
|
|
203
|
+
if (resolutionHasDeletedTerminal(resolution)) inventory.deleted.push(start);
|
|
204
|
+
}
|
|
183
205
|
continue;
|
|
184
206
|
}
|
|
185
207
|
if (resolution.status === 'deleted') {
|
|
@@ -225,12 +247,32 @@ function anchorEntry(anchor, resolution) {
|
|
|
225
247
|
sourcePath: anchor.sourcePath,
|
|
226
248
|
symbolId: anchor.symbolId,
|
|
227
249
|
symbolName: anchor.symbolName,
|
|
250
|
+
sourcePaths: resolutionSourcePaths(resolution, anchor),
|
|
251
|
+
lineageEventIds: uniqueStrings(resolution.traversedEventIds),
|
|
252
|
+
terminalEventIds: uniqueStrings(resolution.terminalEventIds),
|
|
253
|
+
evidenceIds: uniqueStrings(resolution.evidenceIds),
|
|
254
|
+
proofIds: uniqueStrings(resolution.proofIds),
|
|
255
|
+
crdtOperationIds: uniqueStrings(resolution.crdtOperationIds),
|
|
256
|
+
crdtHeads: uniqueStrings(resolution.crdtHeads),
|
|
257
|
+
lineageEventKinds: uniqueStrings(resolution.lineageEventKinds),
|
|
228
258
|
status: resolution.status,
|
|
229
259
|
resolutionId: resolution.id,
|
|
260
|
+
confidence: resolution.confidence,
|
|
230
261
|
reasonCodes: uniqueStrings(resolution.reasonCodes)
|
|
231
262
|
});
|
|
232
263
|
}
|
|
233
264
|
|
|
265
|
+
function resolutionHasInactiveTerminal(resolution) {
|
|
266
|
+
return array(resolution.terminalEventIds).length > 0
|
|
267
|
+
|| resolutionHasDeletedTerminal(resolution)
|
|
268
|
+
|| array(resolution.reasonCodes).some((code) => code === 'lineage-event-without-target-anchor' || code === 'inactive-anchor-has-active-candidates');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function resolutionHasDeletedTerminal(resolution) {
|
|
272
|
+
return array(resolution.lineageEventKinds).includes('deleted')
|
|
273
|
+
|| array(resolution.reasonCodes).includes('anchor-deleted');
|
|
274
|
+
}
|
|
275
|
+
|
|
234
276
|
function queryAnchor(resolution) {
|
|
235
277
|
return compactRecord({
|
|
236
278
|
key: resolution.query?.anchorKey,
|
|
@@ -250,6 +292,17 @@ function uniqueAnchorEntries(entries) {
|
|
|
250
292
|
});
|
|
251
293
|
}
|
|
252
294
|
|
|
295
|
+
function resolutionSourcePaths(resolution, anchor) {
|
|
296
|
+
return uniqueStrings([
|
|
297
|
+
anchor?.sourcePath,
|
|
298
|
+
...array(anchor?.lineageSourcePaths),
|
|
299
|
+
resolution.query?.sourcePath,
|
|
300
|
+
resolution.startAnchor?.sourcePath,
|
|
301
|
+
...array(resolution.sourcePaths),
|
|
302
|
+
...resolution.currentAnchors.flatMap((entry) => [entry.sourcePath, ...array(entry.lineageSourcePaths)])
|
|
303
|
+
]);
|
|
304
|
+
}
|
|
305
|
+
|
|
253
306
|
function array(value) { return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value]; }
|
|
254
307
|
function uniqueStrings(values) { return uniqueRawStrings(array(values).map((value) => String(value ?? '')).filter(Boolean)); }
|
|
255
308
|
function firstString(...values) { return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean); }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{idFragment}from'../../native-import-utils.js';import{semanticOwnershipRegionForDeclaration}from'../../semantic-import-regions.js';import{createSemanticIndexRecord,hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
|
|
1
|
+
import{idFragment,caseSensitiveIdFragment}from'../../native-import-utils.js';import{semanticOwnershipRegionForDeclaration}from'../../semantic-import-regions.js';import{createSemanticIndexRecord,hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
|
|
2
2
|
import{relationPredicateForDeclaration}from'./relationPredicateForDeclaration.js';
|
|
3
3
|
export function semanticIndexFromNativeDeclarations(declarations, input, options) {
|
|
4
4
|
const documentId = `doc_${idFragment(input.sourcePath ?? input.language)}_${idFragment(input.sourceHash)}`;
|
|
@@ -9,7 +9,7 @@ export function semanticIndexFromNativeDeclarations(declarations, input, options
|
|
|
9
9
|
const facts = [];
|
|
10
10
|
const mappings = [];
|
|
11
11
|
for (const declaration of declarations) {
|
|
12
|
-
const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${
|
|
12
|
+
const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${caseSensitiveIdFragment(declaration.name)}`;
|
|
13
13
|
const occurrenceId = `occ_${idFragment(declaration.nativeNode.id)}_${declaration.role ?? 'definition'}`;
|
|
14
14
|
const ownershipRegion = semanticOwnershipRegionForDeclaration(input, {
|
|
15
15
|
...declaration,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { uniqueStrings } from '../../native-import-utils.js';
|
|
2
|
+
|
|
3
|
+
export function addIdentityHashEvidence(before, after, add, note) {
|
|
4
|
+
const matches = matchingIdentityHashReasons(before, after);
|
|
5
|
+
if (matches.length === 0) return;
|
|
6
|
+
if (!compatibleLineageSurface(before, after)) {
|
|
7
|
+
note('identity-hash-match-surface-mismatch');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const primary = matches.includes('semantic-identity-hash-match')
|
|
11
|
+
? 'semantic-identity-hash-match'
|
|
12
|
+
: matches.includes('source-identity-hash-match')
|
|
13
|
+
? 'source-identity-hash-match'
|
|
14
|
+
: 'identity-hash-match';
|
|
15
|
+
add(0.62, primary);
|
|
16
|
+
for (const reason of matches) {
|
|
17
|
+
if (reason !== primary) note(reason);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function addSourceHashEvidence(before, after, add, note, reasons, sameSymbolSurface) {
|
|
22
|
+
const beforeHash = firstString(before.anchor.sourceHash, before.sourceHash);
|
|
23
|
+
const afterHash = firstString(after.anchor.sourceHash, after.sourceHash);
|
|
24
|
+
if (!beforeHash || !afterHash) return;
|
|
25
|
+
if (beforeHash !== afterHash) {
|
|
26
|
+
note('source-hash-changed');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!hasSourceHashSupport(before, after, reasons, sameSymbolSurface)) {
|
|
30
|
+
note('source-hash-match-without-lineage-support');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
add(0.04, 'source-hash-match');
|
|
34
|
+
if (before.anchor.sourcePath && after.anchor.sourcePath && before.anchor.sourcePath !== after.anchor.sourcePath) {
|
|
35
|
+
note('source-hash-preserved-across-path');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function hashEvidenceSummary(reasons) {
|
|
40
|
+
return {
|
|
41
|
+
semanticIdentityHashMatch: reasons.includes('semantic-identity-hash-match'),
|
|
42
|
+
sourceIdentityHashMatch: reasons.includes('source-identity-hash-match'),
|
|
43
|
+
identityHashMatch: reasons.includes('identity-hash-match'),
|
|
44
|
+
sourceHashMatch: reasons.includes('source-hash-match'),
|
|
45
|
+
signatureHashMatch: reasons.includes('signature-hash-match'),
|
|
46
|
+
bodyHashMatch: reasons.includes('body-hash-match')
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function matchingIdentityHashReasons(before, after) {
|
|
51
|
+
const beforeHashes = identityHashEntries(before);
|
|
52
|
+
const afterHashes = new Map(identityHashEntries(after).map((entry) => [entry.value, entry.reason]));
|
|
53
|
+
const reasons = [];
|
|
54
|
+
for (const entry of beforeHashes) {
|
|
55
|
+
const afterReason = afterHashes.get(entry.value);
|
|
56
|
+
if (!afterReason) continue;
|
|
57
|
+
reasons.push(identityHashMatchReason(entry.reason, afterReason));
|
|
58
|
+
}
|
|
59
|
+
return uniqueStrings(reasons);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function identityHashEntries(symbol) {
|
|
63
|
+
return [
|
|
64
|
+
{ reason: 'semantic-identity-hash-match', value: firstString(symbol.semanticIdentityHash, symbol.anchor.metadata?.semanticIdentityHash) },
|
|
65
|
+
{ reason: 'source-identity-hash-match', value: firstString(symbol.sourceIdentityHash, symbol.anchor.metadata?.sourceIdentityHash) },
|
|
66
|
+
{ reason: 'identity-hash-match', value: firstString(symbol.identityHash, symbol.anchor.metadata?.identityHash) }
|
|
67
|
+
].filter((entry) => entry.value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function identityHashMatchReason(beforeReason, afterReason) {
|
|
71
|
+
if (beforeReason === afterReason) return beforeReason;
|
|
72
|
+
if (beforeReason === 'semantic-identity-hash-match' || afterReason === 'semantic-identity-hash-match') return 'semantic-identity-hash-match';
|
|
73
|
+
if (beforeReason === 'source-identity-hash-match' || afterReason === 'source-identity-hash-match') return 'source-identity-hash-match';
|
|
74
|
+
return 'identity-hash-match';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function compatibleLineageSurface(before, after) {
|
|
78
|
+
return (!before.language || !after.language || before.language === after.language)
|
|
79
|
+
&& (!before.kind || !after.kind || before.kind === after.kind)
|
|
80
|
+
&& (!before.anchor.kind || !after.anchor.kind || before.anchor.kind === after.anchor.kind);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function hasSourceHashSupport(before, after, reasons, sameSymbolSurface) {
|
|
84
|
+
return reasons.some((reason) => [
|
|
85
|
+
'semantic-identity-hash-match',
|
|
86
|
+
'source-identity-hash-match',
|
|
87
|
+
'identity-hash-match',
|
|
88
|
+
'signature-hash-match',
|
|
89
|
+
'body-hash-match',
|
|
90
|
+
'symbol-name-match'
|
|
91
|
+
].includes(reason))
|
|
92
|
+
|| sameSymbolSurface(before, after);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function firstString(...values) {
|
|
96
|
+
return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean);
|
|
97
|
+
}
|