@shapeshift-labs/frontier-lang-compiler 0.2.103 → 0.2.104
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/README.md +13 -0
- package/dist/declarations/bidirectional-target-change-source-edit.d.ts +30 -0
- package/dist/declarations/bidirectional-target-change.d.ts +10 -0
- package/dist/declarations/js-ts-safe-member-merge.d.ts +58 -0
- package/dist/declarations/js-ts-safe-merge.d.ts +120 -0
- package/dist/declarations/js-ts-semantic-conflict-sidecars.d.ts +235 -0
- package/dist/declarations/js-ts-semantic-merge-contracts.d.ts +287 -0
- package/dist/declarations/js-ts-semantic-merge.d.ts +4 -0
- package/dist/declarations/native-import-losses.d.ts +3 -0
- package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +12 -0
- package/dist/declarations/semantic-edit-script.d.ts +7 -4
- package/dist/declarations/semantic-patch-bundle-index.d.ts +45 -0
- package/dist/declarations/semantic-patch-bundle.d.ts +6 -4
- package/dist/declarations/semantic-sidecar-example.d.ts +18 -0
- package/dist/declarations/semantic-transform-identity.d.ts +3 -0
- package/dist/declarations/source-preservation.d.ts +72 -0
- package/dist/declarations/universal-capability.d.ts +4 -0
- package/dist/declarations/universal-conversion-artifacts.d.ts +61 -1
- package/dist/declarations/universal-conversion-compact-counts.d.ts +51 -0
- package/dist/declarations/universal-conversion-plan.d.ts +6 -1
- package/dist/declarations/universal-representation-coverage.d.ts +90 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/internal/index-impl/bidirectionalExactSourceBackprojection.js +199 -0
- package/dist/internal/index-impl/bidirectionalSameLanguageSourceProjection.js +112 -0
- package/dist/internal/index-impl/bidirectionalSourceEditProjection.js +319 -0
- package/dist/internal/index-impl/bidirectionalSourceEditProjectionArtifacts.js +67 -0
- package/dist/internal/index-impl/bidirectionalTargetChangeRecordInternals.js +17 -5
- package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +58 -20
- package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +60 -7
- package/dist/internal/index-impl/createLightweightNativeImport.js +1 -0
- package/dist/internal/index-impl/createNativeSourcePreservation.js +28 -2
- package/dist/internal/index-impl/diffNativeSymbols.js +3 -3
- package/dist/internal/index-impl/nativeChangeProjectionSourceMapLinks.js +2 -0
- package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +43 -8
- package/dist/internal/index-impl/replaySemanticEditLineEndings.js +34 -0
- package/dist/internal/index-impl/replaySemanticEditProjection.js +39 -19
- package/dist/internal/index-impl/semanticEditBundleAdmission.js +7 -3
- package/dist/internal/index-impl/semanticEditBundleIndex.js +47 -1
- package/dist/internal/index-impl/semanticEditExplicitSourceReplacement.js +40 -0
- package/dist/internal/index-impl/semanticEditOperationCoverage.js +33 -3
- package/dist/internal/index-impl/semanticEditProjectionRecord.js +29 -0
- package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +39 -0
- package/dist/internal/index-impl/semanticEditReplaySourceReplacement.js +85 -0
- package/dist/internal/index-impl/semanticEditScripts.js +4 -0
- package/dist/internal/index-impl/semanticEditSourceRanges.js +27 -0
- package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +1 -0
- package/dist/internal/index-impl/semanticPatchBundleAdmission.js +41 -7
- package/dist/internal/index-impl/semanticPatchBundleRecords.js +16 -0
- package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +2 -0
- package/dist/internal/index-impl/semanticSidecarQuality.js +111 -0
- package/dist/internal/index-impl/semanticSourceEditDedupe.js +69 -9
- package/dist/internal/index-impl/semanticTransformIdentityRecords.js +85 -9
- package/dist/js-ts-safe-member-merge-result.js +158 -0
- package/dist/js-ts-safe-member-merge.js +202 -0
- package/dist/js-ts-safe-merge-analyze.js +279 -0
- package/dist/js-ts-safe-merge-constants.js +50 -0
- package/dist/js-ts-safe-merge-context.js +118 -0
- package/dist/js-ts-safe-merge-ledger-validation.js +92 -0
- package/dist/js-ts-safe-merge-ledger.js +85 -0
- package/dist/js-ts-safe-merge-parse-declarations.js +210 -0
- package/dist/js-ts-safe-merge-parse-statements.js +155 -0
- package/dist/js-ts-safe-merge-plan.js +190 -0
- package/dist/js-ts-safe-merge.js +175 -0
- package/dist/js-ts-semantic-conflict-sidecar-constants.js +77 -0
- package/dist/js-ts-semantic-conflict-sidecar-detectors.js +195 -0
- package/dist/js-ts-semantic-conflict-sidecar-normalize.js +203 -0
- package/dist/js-ts-semantic-conflict-sidecar-utils.js +190 -0
- package/dist/js-ts-semantic-conflict-sidecars.js +81 -0
- package/dist/js-ts-semantic-merge-contract-helpers.js +128 -0
- package/dist/js-ts-semantic-merge-contracts.js +217 -0
- package/dist/js-ts-semantic-merge-member-containers.js +100 -0
- package/dist/js-ts-semantic-merge-member-keys.js +142 -0
- package/dist/js-ts-semantic-merge-member-segments.js +185 -0
- package/dist/js-ts-semantic-merge-member-source.js +64 -0
- package/dist/js-ts-semantic-merge-member-utils.js +18 -0
- package/dist/js-ts-semantic-merge-parse.js +15 -0
- package/dist/js-ts-semantic-merge.js +21 -0
- package/dist/lightweight-dependency-effects.js +51 -0
- package/dist/lightweight-dependency-language.js +12 -1
- package/dist/lightweight-dependency-relations.js +14 -27
- package/dist/native-region-scanner-core.js +33 -1
- package/dist/native-region-scanner-csharp.js +151 -0
- package/dist/native-region-scanner-dart.js +91 -0
- package/dist/native-region-scanner-dynamic.js +21 -151
- package/dist/native-region-scanner-functional.js +40 -13
- package/dist/native-region-scanner-java.js +97 -0
- package/dist/native-region-scanner-js-class.js +100 -0
- package/dist/native-region-scanner-js-helpers.js +28 -86
- package/dist/native-region-scanner-js-imports.js +121 -1
- package/dist/native-region-scanner-js-nested.js +96 -8
- package/dist/native-region-scanner-js-structure.js +27 -0
- package/dist/native-region-scanner-js-types.js +99 -0
- package/dist/native-region-scanner-js.js +70 -118
- package/dist/native-region-scanner-kotlin.js +94 -0
- package/dist/native-region-scanner-main.js +15 -181
- package/dist/native-region-scanner-php.js +80 -0
- package/dist/native-region-scanner-python.js +62 -0
- package/dist/native-region-scanner-ruby.js +72 -0
- package/dist/native-region-scanner-scala.js +91 -0
- package/dist/native-region-scanner-spans.js +74 -0
- package/dist/native-region-scanner-swift.js +155 -0
- package/dist/native-region-scanner.js +14 -10
- package/dist/native-source-ledger-helpers.js +195 -0
- package/dist/native-source-ledger.js +306 -0
- package/dist/native-source-preservation-scanner.js +4 -0
- package/dist/semantic-import-callsite-regions.js +136 -0
- package/dist/semantic-import-effect-regions.js +283 -0
- package/dist/semantic-import-regions.js +11 -2
- package/dist/semantic-import-sidecar-entry.js +16 -2
- package/dist/semantic-import-sidecar-types.d.ts +2 -0
- package/dist/semantic-sidecar-example.js +68 -0
- package/dist/universal-capability-matrix.js +23 -0
- package/dist/universal-conversion-artifact-query.js +79 -2
- package/dist/universal-conversion-artifact-semantic-edit.js +103 -0
- package/dist/universal-conversion-artifact-summary.js +33 -1
- package/dist/universal-conversion-artifacts.js +13 -48
- package/dist/universal-conversion-plan-scoring.js +21 -1
- package/dist/universal-conversion-plan-summary.js +30 -0
- package/dist/universal-conversion-plan.js +25 -9
- package/dist/universal-conversion-route-metadata.js +96 -0
- package/dist/universal-conversion-route-operations.js +7 -0
- package/dist/universal-representation-coverage.js +193 -0
- package/package.json +1 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds } from './js-ts-safe-merge-constants.js';
|
|
2
|
+
import { addConflict, detectLineEnding, normalizeLineEndings } from './js-ts-safe-merge-context.js';
|
|
3
|
+
import { importSpecifierCanonical } from './js-ts-safe-merge-ledger.js';
|
|
4
|
+
|
|
5
|
+
export function createSourceMergePlan(base, worker, head, workerPlan, headPlan, context) {
|
|
6
|
+
const edits = [];
|
|
7
|
+
let importSpecifierAdditions = 0;
|
|
8
|
+
for (const baseEntry of base.entries.filter((entry) => entry.kind === 'import')) {
|
|
9
|
+
const workerAdditions = workerPlan.importAdditions.get(baseEntry.key) ?? [];
|
|
10
|
+
const headAdditions = headPlan.importAdditions.get(baseEntry.key) ?? [];
|
|
11
|
+
if (!workerAdditions.length && !headAdditions.length) continue;
|
|
12
|
+
importSpecifierAdditions += workerAdditions.length + headAdditions.length;
|
|
13
|
+
const headEntry = head.entries.find((entry) => entry.key === baseEntry.key);
|
|
14
|
+
if (!headEntry) {
|
|
15
|
+
addConflict(context, {
|
|
16
|
+
code: JsTsSafeMergeConflictCodes.insertionAnchorMissing,
|
|
17
|
+
gateId: JsTsSafeMergeGateIds.resolvedInsertionAnchors,
|
|
18
|
+
side: 'head',
|
|
19
|
+
message: 'Head source is missing a base import anchor.',
|
|
20
|
+
details: { key: baseEntry.key }
|
|
21
|
+
});
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
edits.push({
|
|
25
|
+
kind: 'replace-import',
|
|
26
|
+
start: headEntry.start,
|
|
27
|
+
end: headEntry.end,
|
|
28
|
+
text: renderImportStatement(baseEntry.importInfo, mergedImportSpecifiers(baseEntry.importInfo.specifiers, workerAdditions, headAdditions))
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const insertionGroups = variantInsertionGroups(worker, workerPlan.baseKeys, 'worker', context);
|
|
33
|
+
const headInsertionGroups = variantInsertionGroups(head, headPlan.baseKeys, 'head', context);
|
|
34
|
+
const headInsertionGroupsByAnchor = new Map(headInsertionGroups.map((group) => [insertionGroupKey(group), group]));
|
|
35
|
+
const headEntriesByKey = new Map(head.entries.map((entry) => [entry.key, entry]));
|
|
36
|
+
let topLevelDeclarationAdditions = 0;
|
|
37
|
+
for (const group of insertionGroups) {
|
|
38
|
+
const anchor = headEntriesByKey.get(group.anchorKey);
|
|
39
|
+
if (!anchor) {
|
|
40
|
+
addConflict(context, {
|
|
41
|
+
code: JsTsSafeMergeConflictCodes.insertionAnchorMissing,
|
|
42
|
+
gateId: JsTsSafeMergeGateIds.resolvedInsertionAnchors,
|
|
43
|
+
side: 'head',
|
|
44
|
+
message: 'Head source is missing a declaration insertion anchor.',
|
|
45
|
+
details: { anchorKey: group.anchorKey, mode: group.mode }
|
|
46
|
+
});
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const headGroup = headInsertionGroupsByAnchor.get(insertionGroupKey(group));
|
|
50
|
+
const insertionSpan = declarationInsertionSpan(head.sourceText, anchor, group.mode, headGroup);
|
|
51
|
+
const entries = mergedDeclarationEntries(group.entries, headGroup?.entries ?? []);
|
|
52
|
+
edits.push({
|
|
53
|
+
kind: 'insert-declarations',
|
|
54
|
+
start: insertionSpan.start,
|
|
55
|
+
end: insertionSpan.end,
|
|
56
|
+
text: declarationInsertionText(entries, detectLineEnding(head.sourceText))
|
|
57
|
+
});
|
|
58
|
+
topLevelDeclarationAdditions += group.entries.length;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
edits,
|
|
63
|
+
importSpecifierAdditions,
|
|
64
|
+
topLevelDeclarationAdditions
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function variantInsertionGroups(variant, baseKeys, side, context) {
|
|
69
|
+
const groups = [];
|
|
70
|
+
let index = 0;
|
|
71
|
+
while (index < variant.entries.length) {
|
|
72
|
+
const entry = variant.entries[index];
|
|
73
|
+
if (baseKeys.has(entry.key)) {
|
|
74
|
+
index += 1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const start = index;
|
|
78
|
+
while (index < variant.entries.length && !baseKeys.has(variant.entries[index].key)) index += 1;
|
|
79
|
+
const entries = variant.entries.slice(start, index);
|
|
80
|
+
const previousBase = findPreviousBaseEntry(variant.entries, start, baseKeys);
|
|
81
|
+
const nextBase = findNextBaseEntry(variant.entries, index, baseKeys);
|
|
82
|
+
if (!previousBase && !nextBase) {
|
|
83
|
+
addConflict(context, {
|
|
84
|
+
code: JsTsSafeMergeConflictCodes.ambiguousInsertionPoint,
|
|
85
|
+
gateId: JsTsSafeMergeGateIds.resolvedInsertionAnchors,
|
|
86
|
+
side,
|
|
87
|
+
message: `${side} additions have no stable base declaration or import anchor.`,
|
|
88
|
+
details: { entries: entries.map((item) => item.names?.[0] ?? item.key) }
|
|
89
|
+
});
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
groups.push(previousBase
|
|
93
|
+
? { mode: 'after', anchorKey: previousBase.key, entries }
|
|
94
|
+
: { mode: 'before', anchorKey: nextBase.key, entries });
|
|
95
|
+
}
|
|
96
|
+
return groups;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function insertionGroupKey(group) {
|
|
100
|
+
return `${group.mode}:${group.anchorKey}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function findPreviousBaseEntry(entries, startIndex, baseKeys) {
|
|
104
|
+
for (let index = startIndex - 1; index >= 0; index -= 1) {
|
|
105
|
+
if (baseKeys.has(entries[index].key)) return entries[index];
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function findNextBaseEntry(entries, startIndex, baseKeys) {
|
|
111
|
+
for (let index = startIndex; index < entries.length; index += 1) {
|
|
112
|
+
if (baseKeys.has(entries[index].key)) return entries[index];
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function applySourceMergePlan(sourceText, mergePlan) {
|
|
118
|
+
return [...mergePlan.edits]
|
|
119
|
+
.sort((left, right) => right.start - left.start || right.end - left.end)
|
|
120
|
+
.reduce((current, edit) => `${current.slice(0, edit.start)}${edit.text}${current.slice(edit.end)}`, sourceText);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function mergedImportSpecifiers(baseSpecifiers, workerAdditions, headAdditions) {
|
|
124
|
+
const seen = new Set(baseSpecifiers.map((specifier) => specifier.canonical));
|
|
125
|
+
const additions = [];
|
|
126
|
+
for (const specifier of [...workerAdditions, ...headAdditions]) {
|
|
127
|
+
if (seen.has(specifier.canonical)) continue;
|
|
128
|
+
seen.add(specifier.canonical);
|
|
129
|
+
additions.push(specifier);
|
|
130
|
+
}
|
|
131
|
+
additions.sort((left, right) => left.canonical.localeCompare(right.canonical));
|
|
132
|
+
return [...baseSpecifiers, ...additions];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function renderImportStatement(importInfo, specifiers) {
|
|
136
|
+
const clause = [];
|
|
137
|
+
if (importInfo.defaultLocalName) clause.push(importInfo.defaultLocalName);
|
|
138
|
+
if (importInfo.namespaceLocalName) clause.push(`* as ${importInfo.namespaceLocalName}`);
|
|
139
|
+
if (specifiers.length) clause.push(`{ ${specifiers.map(importSpecifierCanonical).join(', ')} }`);
|
|
140
|
+
const importType = importInfo.typeOnly ? 'type ' : '';
|
|
141
|
+
return `import ${importType}${clause.join(', ')} from ${importInfo.quote}${importInfo.moduleSpecifier}${importInfo.quote};`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function declarationInsertionText(entries, lineEnding) {
|
|
145
|
+
return entries
|
|
146
|
+
.map((entry) => normalizeLineEndings(entry.text.trimEnd(), lineEnding))
|
|
147
|
+
.map((text) => text.endsWith(lineEnding) ? text : `${text}${lineEnding}`)
|
|
148
|
+
.join('');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function mergedDeclarationEntries(workerEntries, headEntries) {
|
|
152
|
+
const entriesByKey = new Map();
|
|
153
|
+
for (const entry of [...workerEntries, ...headEntries]) {
|
|
154
|
+
const key = `${entry.kind}:${entry.key}:${entry.text.trim()}`;
|
|
155
|
+
if (!entriesByKey.has(key)) entriesByKey.set(key, entry);
|
|
156
|
+
}
|
|
157
|
+
return [...entriesByKey.values()].sort((left, right) => {
|
|
158
|
+
const keyOrder = left.key.localeCompare(right.key);
|
|
159
|
+
if (keyOrder !== 0) return keyOrder;
|
|
160
|
+
return left.text.trim().localeCompare(right.text.trim());
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function declarationInsertionSpan(sourceText, anchor, mode, headGroup) {
|
|
165
|
+
if (!headGroup?.entries.length) {
|
|
166
|
+
const offset = mode === 'after'
|
|
167
|
+
? offsetAfterEntryLine(sourceText, anchor)
|
|
168
|
+
: offsetBeforeEntryLine(sourceText, anchor);
|
|
169
|
+
return { start: offset, end: offset };
|
|
170
|
+
}
|
|
171
|
+
if (mode === 'after') {
|
|
172
|
+
return {
|
|
173
|
+
start: offsetAfterEntryLine(sourceText, anchor),
|
|
174
|
+
end: offsetAfterEntryLine(sourceText, headGroup.entries[headGroup.entries.length - 1])
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
start: offsetBeforeEntryLine(sourceText, headGroup.entries[0]),
|
|
179
|
+
end: offsetBeforeEntryLine(sourceText, anchor)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function offsetAfterEntryLine(sourceText, entry) {
|
|
184
|
+
const lineEnd = sourceText.indexOf('\n', entry.end);
|
|
185
|
+
return lineEnd === -1 ? sourceText.length : lineEnd + 1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function offsetBeforeEntryLine(sourceText, entry) {
|
|
189
|
+
return sourceText.lastIndexOf('\n', Math.max(0, entry.start - 1)) + 1;
|
|
190
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { analyzeVariantLedger, validateIndependentAdditions } from './js-ts-safe-merge-analyze.js';
|
|
2
|
+
import { addConflict, blockedResult, compactRecord, createMergeContext, gatesFor } from './js-ts-safe-merge-context.js';
|
|
3
|
+
import {
|
|
4
|
+
JsTsSafeMergeConflictCodes,
|
|
5
|
+
JsTsSafeMergeGateIds,
|
|
6
|
+
JsTsSafeMergeStatuses,
|
|
7
|
+
jsTsSafeMergeGateOrder
|
|
8
|
+
} from './js-ts-safe-merge-constants.js';
|
|
9
|
+
import { indexBaseLedger, scanJsTsTopLevelLedger, validateLedgerUniqueness } from './js-ts-safe-merge-ledger.js';
|
|
10
|
+
import { applySourceMergePlan, createSourceMergePlan } from './js-ts-safe-merge-plan.js';
|
|
11
|
+
|
|
12
|
+
export { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds, JsTsSafeMergeStatuses };
|
|
13
|
+
|
|
14
|
+
export function safeMergeJsTsImportsAndDeclarations(input = {}) {
|
|
15
|
+
const context = createMergeContext(input);
|
|
16
|
+
const baseSourceText = input.baseSourceText;
|
|
17
|
+
const workerSourceText = input.workerSourceText;
|
|
18
|
+
const headSourceText = input.headSourceText;
|
|
19
|
+
|
|
20
|
+
if (typeof baseSourceText !== 'string' || typeof workerSourceText !== 'string' || typeof headSourceText !== 'string') {
|
|
21
|
+
addConflict(context, {
|
|
22
|
+
code: JsTsSafeMergeConflictCodes.invalidInput,
|
|
23
|
+
gateId: JsTsSafeMergeGateIds.parseLedger,
|
|
24
|
+
message: 'baseSourceText, workerSourceText, and headSourceText must be strings.',
|
|
25
|
+
details: {
|
|
26
|
+
baseSourceText: typeof baseSourceText,
|
|
27
|
+
workerSourceText: typeof workerSourceText,
|
|
28
|
+
headSourceText: typeof headSourceText
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return blockedResult(context);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
validateStaleSourceHashes(input, context);
|
|
35
|
+
validateSourceLedgerSpans(input, context);
|
|
36
|
+
if (context.conflicts.length) return blockedResult(context);
|
|
37
|
+
|
|
38
|
+
const base = scanJsTsTopLevelLedger(baseSourceText, 'base', context);
|
|
39
|
+
const worker = scanJsTsTopLevelLedger(workerSourceText, 'worker', context);
|
|
40
|
+
const head = scanJsTsTopLevelLedger(headSourceText, 'head', context);
|
|
41
|
+
if (context.conflicts.length) return blockedResult(context, { base, worker, head });
|
|
42
|
+
|
|
43
|
+
validateLedgerUniqueness(base, context);
|
|
44
|
+
validateLedgerUniqueness(worker, context);
|
|
45
|
+
validateLedgerUniqueness(head, context);
|
|
46
|
+
if (context.conflicts.length) return blockedResult(context, { base, worker, head });
|
|
47
|
+
|
|
48
|
+
const baseIndex = indexBaseLedger(base, context);
|
|
49
|
+
if (context.conflicts.length) return blockedResult(context, { base, worker, head });
|
|
50
|
+
|
|
51
|
+
const workerPlan = analyzeVariantLedger(base, worker, baseIndex, 'worker', context);
|
|
52
|
+
const headPlan = analyzeVariantLedger(base, head, baseIndex, 'head', context);
|
|
53
|
+
validateIndependentAdditions(base, workerPlan, headPlan, context);
|
|
54
|
+
if (context.conflicts.length) return blockedResult(context, { base, worker, head });
|
|
55
|
+
|
|
56
|
+
const mergePlan = createSourceMergePlan(base, worker, head, workerPlan, headPlan, context);
|
|
57
|
+
if (context.conflicts.length) return blockedResult(context, { base, worker, head });
|
|
58
|
+
|
|
59
|
+
const mergedSourceText = applySourceMergePlan(headSourceText, mergePlan);
|
|
60
|
+
const merged = scanJsTsTopLevelLedger(mergedSourceText, 'merged', context);
|
|
61
|
+
if (!context.conflicts.length) validateLedgerUniqueness(merged, context);
|
|
62
|
+
if (context.conflicts.length) return blockedResult(context, { base, worker, head, merged });
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
kind: 'frontier.lang.jsTsSafeMerge',
|
|
66
|
+
version: 1,
|
|
67
|
+
schema: 'frontier.lang.jsTsSafeMerge.v1',
|
|
68
|
+
id: context.id,
|
|
69
|
+
status: JsTsSafeMergeStatuses.merged,
|
|
70
|
+
sourcePath: context.sourcePath,
|
|
71
|
+
language: context.language,
|
|
72
|
+
mergedSourceText,
|
|
73
|
+
outputSourceText: mergedSourceText,
|
|
74
|
+
conflicts: [],
|
|
75
|
+
gates: gatesFor(context),
|
|
76
|
+
admission: {
|
|
77
|
+
status: 'auto-merge-candidate',
|
|
78
|
+
action: 'apply',
|
|
79
|
+
reviewRequired: false,
|
|
80
|
+
autoApplyCandidate: true,
|
|
81
|
+
autoMergeClaim: false,
|
|
82
|
+
semanticEquivalenceClaim: false,
|
|
83
|
+
reasonCodes: []
|
|
84
|
+
},
|
|
85
|
+
summary: {
|
|
86
|
+
importSpecifierAdditions: mergePlan.importSpecifierAdditions,
|
|
87
|
+
topLevelDeclarationAdditions: mergePlan.topLevelDeclarationAdditions,
|
|
88
|
+
changedExistingDeclarations: 0,
|
|
89
|
+
conflicts: 0,
|
|
90
|
+
gatesPassed: jsTsSafeMergeGateOrder.length
|
|
91
|
+
},
|
|
92
|
+
metadata: compactRecord({
|
|
93
|
+
workerChangeSetId: input.workerChangeSetId,
|
|
94
|
+
headChangeSetId: input.headChangeSetId,
|
|
95
|
+
baseHash: input.baseHash,
|
|
96
|
+
workerHash: input.workerHash,
|
|
97
|
+
headHash: input.headHash,
|
|
98
|
+
expectedSourceHash: input.expectedSourceHash,
|
|
99
|
+
currentSourceHash: input.currentSourceHash
|
|
100
|
+
})
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function validateStaleSourceHashes(input, context) {
|
|
105
|
+
for (const pair of [
|
|
106
|
+
['expectedSourceHash', 'currentSourceHash', 'head'],
|
|
107
|
+
['expectedHeadHash', 'headHash', 'head'],
|
|
108
|
+
['expectedBaseHash', 'baseHash', 'base'],
|
|
109
|
+
['expectedWorkerHash', 'workerHash', 'worker']
|
|
110
|
+
]) {
|
|
111
|
+
const [expectedField, currentField, side] = pair;
|
|
112
|
+
const expected = input[expectedField];
|
|
113
|
+
const current = input[currentField];
|
|
114
|
+
if (typeof expected !== 'string' || typeof current !== 'string' || expected === current) continue;
|
|
115
|
+
addConflict(context, {
|
|
116
|
+
code: JsTsSafeMergeConflictCodes.staleSourceHash,
|
|
117
|
+
gateId: JsTsSafeMergeGateIds.parseLedger,
|
|
118
|
+
side,
|
|
119
|
+
message: `${side} source hash is stale for safe merge anchors.`,
|
|
120
|
+
details: { expectedField, currentField, expected, current }
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function validateSourceLedgerSpans(input, context) {
|
|
126
|
+
if (!input.requireSourceLedgerSpans && !input.sourceLedgers && !input.sourceLedger) return;
|
|
127
|
+
for (const side of ['base', 'worker', 'head']) {
|
|
128
|
+
const ledger = sourceLedgerForSide(input, side);
|
|
129
|
+
if (!ledger) {
|
|
130
|
+
addConflict(context, {
|
|
131
|
+
code: JsTsSafeMergeConflictCodes.missingSourceLedgerSpan,
|
|
132
|
+
gateId: JsTsSafeMergeGateIds.parseLedger,
|
|
133
|
+
side,
|
|
134
|
+
message: `${side} source is missing source ledger span evidence.`,
|
|
135
|
+
details: { side, missing: 'source-ledger' }
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const spans = Array.isArray(ledger.spans) ? ledger.spans : Array.isArray(ledger.entries) ? ledger.entries : [];
|
|
140
|
+
if (!spans.length) {
|
|
141
|
+
addConflict(context, {
|
|
142
|
+
code: JsTsSafeMergeConflictCodes.missingSourceLedgerSpan,
|
|
143
|
+
gateId: JsTsSafeMergeGateIds.parseLedger,
|
|
144
|
+
side,
|
|
145
|
+
message: `${side} source ledger does not include span entries.`,
|
|
146
|
+
details: { side, missing: 'spans' }
|
|
147
|
+
});
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const missingIndex = spans.findIndex((entry) => !hasLedgerSpan(entry));
|
|
151
|
+
if (missingIndex >= 0) {
|
|
152
|
+
addConflict(context, {
|
|
153
|
+
code: JsTsSafeMergeConflictCodes.missingSourceLedgerSpan,
|
|
154
|
+
gateId: JsTsSafeMergeGateIds.parseLedger,
|
|
155
|
+
side,
|
|
156
|
+
message: `${side} source ledger contains an entry without a source span.`,
|
|
157
|
+
details: { side, index: missingIndex, id: spans[missingIndex]?.id, kind: spans[missingIndex]?.kind }
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function sourceLedgerForSide(input, side) {
|
|
164
|
+
return input.sourceLedgers?.[side]
|
|
165
|
+
?? input[`${side}SourceLedger`]
|
|
166
|
+
?? (input.sourceLedgerSide === side ? input.sourceLedger : undefined);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function hasLedgerSpan(entry) {
|
|
170
|
+
const span = entry?.span ?? entry?.sourceSpan;
|
|
171
|
+
if (!span) return false;
|
|
172
|
+
const hasOffsets = Number.isFinite(span.start) && Number.isFinite(span.end);
|
|
173
|
+
const hasLines = Number.isFinite(span.startLine) && Number.isFinite(span.endLine);
|
|
174
|
+
return hasOffsets || hasLines;
|
|
175
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const JsTsSemanticConflictSidecarClasses = Object.freeze([
|
|
2
|
+
'same-region',
|
|
3
|
+
'delete-modify',
|
|
4
|
+
'duplicate-export',
|
|
5
|
+
'duplicate-member',
|
|
6
|
+
'ordered-list-conflict',
|
|
7
|
+
'parser-ledger-loss',
|
|
8
|
+
'stale-source-hash',
|
|
9
|
+
'unsupported-syntax'
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export const classDefaults = Object.freeze({
|
|
13
|
+
'same-region': {
|
|
14
|
+
severity: 'error',
|
|
15
|
+
risk: 'high',
|
|
16
|
+
readiness: 'blocked',
|
|
17
|
+
reasonCodes: ['same-region-concurrent-edit'],
|
|
18
|
+
suggestedOutcome: 'manual-merge-required'
|
|
19
|
+
},
|
|
20
|
+
'delete-modify': {
|
|
21
|
+
severity: 'error',
|
|
22
|
+
risk: 'high',
|
|
23
|
+
readiness: 'blocked',
|
|
24
|
+
reasonCodes: ['delete-modify-same-region'],
|
|
25
|
+
suggestedOutcome: 'choose-delete-or-port-modification'
|
|
26
|
+
},
|
|
27
|
+
'duplicate-export': {
|
|
28
|
+
severity: 'error',
|
|
29
|
+
risk: 'high',
|
|
30
|
+
readiness: 'blocked',
|
|
31
|
+
reasonCodes: ['duplicate-export-name'],
|
|
32
|
+
suggestedOutcome: 'rename-or-remove-duplicate-export'
|
|
33
|
+
},
|
|
34
|
+
'duplicate-member': {
|
|
35
|
+
severity: 'error',
|
|
36
|
+
risk: 'high',
|
|
37
|
+
readiness: 'blocked',
|
|
38
|
+
reasonCodes: ['duplicate-member-name'],
|
|
39
|
+
suggestedOutcome: 'rename-or-remove-duplicate-member'
|
|
40
|
+
},
|
|
41
|
+
'ordered-list-conflict': {
|
|
42
|
+
severity: 'warning',
|
|
43
|
+
risk: 'medium',
|
|
44
|
+
readiness: 'needs-review',
|
|
45
|
+
reasonCodes: ['ordered-list-concurrent-position'],
|
|
46
|
+
suggestedOutcome: 'preserve-intended-order-with-human-review'
|
|
47
|
+
},
|
|
48
|
+
'parser-ledger-loss': {
|
|
49
|
+
severity: 'error',
|
|
50
|
+
risk: 'high',
|
|
51
|
+
readiness: 'blocked',
|
|
52
|
+
reasonCodes: ['parser-or-ledger-loss'],
|
|
53
|
+
suggestedOutcome: 'rerun-parser-and-ledger-before-merge'
|
|
54
|
+
},
|
|
55
|
+
'stale-source-hash': {
|
|
56
|
+
severity: 'error',
|
|
57
|
+
risk: 'high',
|
|
58
|
+
readiness: 'blocked',
|
|
59
|
+
reasonCodes: ['stale-source-hash'],
|
|
60
|
+
suggestedOutcome: 'rerun-semantic-import-before-merge'
|
|
61
|
+
},
|
|
62
|
+
'unsupported-syntax': {
|
|
63
|
+
severity: 'error',
|
|
64
|
+
risk: 'high',
|
|
65
|
+
readiness: 'blocked',
|
|
66
|
+
reasonCodes: ['unsupported-js-ts-syntax'],
|
|
67
|
+
suggestedOutcome: 'fall-back-to-textual-review'
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export const severityRank = Object.freeze({ info: 1, warning: 2, error: 3 });
|
|
72
|
+
export const riskRank = Object.freeze({ low: 1, medium: 2, high: 3 });
|
|
73
|
+
export const orderedChangeKinds = /^(?:insert|add|move|reorder|replace|modify|update)$/i;
|
|
74
|
+
export const deleteChangeKinds = /^(?:delete|remove|removed|deleted)$/i;
|
|
75
|
+
export const modifyChangeKinds = /^(?:modify|modified|replace|update|edit|move|reorder|insert|add)$/i;
|
|
76
|
+
export const parserLedgerLossPattern = /(?:parser|parse|ledger|source[-_ ]?map|anchor|projection).*(?:loss|lost|missing|failed|error)|(?:loss|lost|missing).*(?:parser|ledger|source[-_ ]?map|anchor|projection)/i;
|
|
77
|
+
export const unsupportedSyntaxPattern = /unsupported|unhandled|unknown syntax|syntax unsupported/i;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { idFragment, normalizeSemanticMergeReadiness, uniqueStrings } from './native-import-utils.js';
|
|
2
|
+
import { classDefaults, JsTsSemanticConflictSidecarClasses, orderedChangeKinds, parserLedgerLossPattern, unsupportedSyntaxPattern } from './js-ts-semantic-conflict-sidecar-constants.js';
|
|
3
|
+
import { affectedFromEntries, normalizeAffected, normalizeChange, normalizeDeclaration, sameChangeRegion } from './js-ts-semantic-conflict-sidecar-normalize.js';
|
|
4
|
+
import { array, changePairs, compactRecord, duplicateGroups, firstString, isDeleteChange, isInsertChange, isModifyChange, lossReasonCodes, lossText, normalizeRisk, normalizeSeverity, strings } from './js-ts-semantic-conflict-sidecar-utils.js';
|
|
5
|
+
|
|
6
|
+
export function explicitSidecars(input, context) {
|
|
7
|
+
return [
|
|
8
|
+
...array(input.conflicts),
|
|
9
|
+
...array(input.sidecars),
|
|
10
|
+
...array(input.conflictSidecars)
|
|
11
|
+
]
|
|
12
|
+
.filter((record) => JsTsSemanticConflictSidecarClasses.includes(record?.class))
|
|
13
|
+
.map((record) => conflictSidecar(record.class, record, context));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function sameRegionSidecars(changes, context) {
|
|
17
|
+
const records = [];
|
|
18
|
+
for (const [left, right] of changePairs(changes)) {
|
|
19
|
+
if (!sameChangeRegion(left, right)) continue;
|
|
20
|
+
records.push(conflictSidecar('same-region', {
|
|
21
|
+
id: `js_ts_conflict_${idFragment(left.id)}_${idFragment(right.id)}_same_region`,
|
|
22
|
+
affected: affectedFromEntries([left, right], context),
|
|
23
|
+
reasonCodes: ['same-region-concurrent-edit', ...left.reasonCodes, ...right.reasonCodes],
|
|
24
|
+
metadata: { leftChangeId: left.id, rightChangeId: right.id, leftChangeKind: left.changeKind, rightChangeKind: right.changeKind }
|
|
25
|
+
}, context));
|
|
26
|
+
}
|
|
27
|
+
return records;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function deleteModifySidecars(changes, context) {
|
|
31
|
+
const records = [];
|
|
32
|
+
for (const [left, right] of changePairs(changes)) {
|
|
33
|
+
if (!sameChangeRegion(left, right)) continue;
|
|
34
|
+
if (!(isDeleteChange(left) && isModifyChange(right)) && !(isDeleteChange(right) && isModifyChange(left))) continue;
|
|
35
|
+
const deleted = isDeleteChange(left) ? left : right;
|
|
36
|
+
const modified = deleted === left ? right : left;
|
|
37
|
+
records.push(conflictSidecar('delete-modify', {
|
|
38
|
+
id: `js_ts_conflict_${idFragment(deleted.id)}_${idFragment(modified.id)}_delete_modify`,
|
|
39
|
+
affected: affectedFromEntries([deleted, modified], context),
|
|
40
|
+
reasonCodes: ['delete-modify-same-region', ...deleted.reasonCodes, ...modified.reasonCodes],
|
|
41
|
+
metadata: { deletedChangeId: deleted.id, modifiedChangeId: modified.id, deletedChangeKind: deleted.changeKind, modifiedChangeKind: modified.changeKind }
|
|
42
|
+
}, context));
|
|
43
|
+
}
|
|
44
|
+
return records;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function duplicateDeclarationSidecars(input, changes, context) {
|
|
48
|
+
const declarations = [
|
|
49
|
+
...array(input.declarations),
|
|
50
|
+
...array(input.exports).map((entry) => ({ ...entry, exported: true })),
|
|
51
|
+
...array(input.members).map((entry) => ({ ...entry, member: true })),
|
|
52
|
+
...array(input.symbols),
|
|
53
|
+
...changes.filter((change) => isInsertChange(change) && (change.memberName || change.exportName || change.symbolName))
|
|
54
|
+
].map((entry, index) => normalizeDeclaration(entry, index, context)).filter((entry) => entry.name);
|
|
55
|
+
return [
|
|
56
|
+
...duplicateGroups(declarations.filter((entry) => entry.exported), (entry) => [entry.sourcePath, entry.name].join('|'))
|
|
57
|
+
.map((group) => duplicateRecord('duplicate-export', group, context)),
|
|
58
|
+
...duplicateGroups(declarations.filter((entry) => entry.member || entry.containerKey), (entry) => [entry.sourcePath, entry.containerKey, entry.name].join('|'))
|
|
59
|
+
.map((group) => duplicateRecord('duplicate-member', group, context))
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function orderedListSidecars(input, changes, context) {
|
|
64
|
+
const explicitLists = array(input.orderedLists).flatMap((list, listIndex) =>
|
|
65
|
+
array(list.changes ?? list.operations ?? list.edits).map((change, changeIndex) => normalizeChange({
|
|
66
|
+
...change,
|
|
67
|
+
listKey: change.listKey ?? list.key ?? list.id,
|
|
68
|
+
orderedListKey: change.orderedListKey ?? list.key ?? list.id,
|
|
69
|
+
sourcePath: change.sourcePath ?? list.sourcePath,
|
|
70
|
+
sourceSpan: change.sourceSpan ?? list.sourceSpan,
|
|
71
|
+
id: change.id ?? `${list.id ?? list.key ?? 'ordered_list'}_${changeIndex}`,
|
|
72
|
+
side: change.side ?? change.author ?? `list_${listIndex}`
|
|
73
|
+
}, context, `ordered_list_${listIndex}_${changeIndex}`))
|
|
74
|
+
);
|
|
75
|
+
const orderedChanges = [...changes, ...explicitLists].filter((change) =>
|
|
76
|
+
change.listKey && orderedChangeKinds.test(change.changeKind)
|
|
77
|
+
);
|
|
78
|
+
const groups = duplicateGroups(orderedChanges, (change) => [
|
|
79
|
+
change.sourcePath,
|
|
80
|
+
change.listKey,
|
|
81
|
+
firstString(change.index, change.position, change.orderKey, change.beforeKey, change.afterKey, change.anchorKey)
|
|
82
|
+
].join('|'));
|
|
83
|
+
return groups.map((group) => conflictSidecar('ordered-list-conflict', {
|
|
84
|
+
id: `js_ts_conflict_${idFragment(group[0].listKey)}_${idFragment(firstString(group[0].index, group[0].position, group[0].orderKey, 'position'))}_ordered_list`,
|
|
85
|
+
affected: affectedFromEntries(group, context),
|
|
86
|
+
reasonCodes: ['ordered-list-concurrent-position', ...group.flatMap((entry) => entry.reasonCodes)],
|
|
87
|
+
metadata: {
|
|
88
|
+
listKey: group[0].listKey,
|
|
89
|
+
position: firstString(group[0].index, group[0].position, group[0].orderKey, group[0].beforeKey, group[0].afterKey),
|
|
90
|
+
changeIds: group.map((entry) => entry.id)
|
|
91
|
+
}
|
|
92
|
+
}, context));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function parserLedgerLossSidecars(input, context) {
|
|
96
|
+
const losses = [
|
|
97
|
+
...array(input.parserLosses),
|
|
98
|
+
...array(input.ledgerLosses),
|
|
99
|
+
...array(input.losses),
|
|
100
|
+
...array(input.diagnostics)
|
|
101
|
+
].filter((entry) => parserLedgerLossPattern.test(lossText(entry)) || entry?.kind === 'parser-ledger-loss');
|
|
102
|
+
if (!losses.length) return [];
|
|
103
|
+
return [conflictSidecar('parser-ledger-loss', {
|
|
104
|
+
id: `js_ts_conflict_${idFragment(context.sourcePath ?? context.language ?? 'source')}_parser_ledger_loss`,
|
|
105
|
+
severity: losses.some((entry) => entry.severity === 'error' || entry.status === 'failed') ? 'error' : undefined,
|
|
106
|
+
affected: affectedFromEntries(losses, context),
|
|
107
|
+
reasonCodes: ['parser-or-ledger-loss', ...losses.flatMap(lossReasonCodes)],
|
|
108
|
+
metadata: { lossIds: uniqueStrings(losses.map((entry) => entry.id)), lossKinds: uniqueStrings(losses.map((entry) => entry.kind ?? entry.code)) }
|
|
109
|
+
}, context)];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function staleSourceHashSidecars(input, context) {
|
|
113
|
+
if (!context.expectedSourceHash || !context.currentSourceHash) return [];
|
|
114
|
+
if (context.expectedSourceHash === context.currentSourceHash && input.staleSourceHash !== true) return [];
|
|
115
|
+
return [conflictSidecar('stale-source-hash', {
|
|
116
|
+
id: `js_ts_conflict_${idFragment(context.sourcePath ?? context.language ?? 'source')}_stale_source_hash`,
|
|
117
|
+
affected: affectedFromEntries([{ sourcePath: context.sourcePath, sourceHash: context.currentSourceHash, key: context.sourcePath }], context),
|
|
118
|
+
reasonCodes: ['stale-source-hash', 'expected-source-hash-mismatch'],
|
|
119
|
+
metadata: { expectedSourceHash: context.expectedSourceHash, currentSourceHash: context.currentSourceHash }
|
|
120
|
+
}, context)];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function unsupportedSyntaxSidecars(input, context) {
|
|
124
|
+
const entries = [
|
|
125
|
+
...array(input.unsupportedSyntax),
|
|
126
|
+
...array(input.syntaxUnsupported),
|
|
127
|
+
...array(input.syntaxLosses),
|
|
128
|
+
...array(input.losses).filter((entry) => unsupportedSyntaxPattern.test(lossText(entry)))
|
|
129
|
+
];
|
|
130
|
+
if (!entries.length) return [];
|
|
131
|
+
return entries.map((entry, index) => conflictSidecar('unsupported-syntax', {
|
|
132
|
+
id: entry.id ?? `js_ts_conflict_${idFragment(context.sourcePath ?? context.language ?? 'source')}_${index + 1}_unsupported_syntax`,
|
|
133
|
+
affected: affectedFromEntries([entry], context),
|
|
134
|
+
reasonCodes: ['unsupported-js-ts-syntax', ...lossReasonCodes(entry)],
|
|
135
|
+
metadata: compactRecord({
|
|
136
|
+
syntaxKind: entry.kind ?? entry.syntaxKind,
|
|
137
|
+
parser: entry.parser,
|
|
138
|
+
message: entry.message ?? entry.summary
|
|
139
|
+
})
|
|
140
|
+
}, context));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function duplicateRecord(conflictClass, group, context) {
|
|
144
|
+
return conflictSidecar(conflictClass, {
|
|
145
|
+
id: `js_ts_conflict_${idFragment(group[0].sourcePath ?? context.sourcePath ?? 'source')}_${idFragment(group[0].containerKey ?? 'export')}_${idFragment(group[0].name)}_${conflictClass.replace(/-/g, '_')}`,
|
|
146
|
+
affected: affectedFromEntries(group, context),
|
|
147
|
+
reasonCodes: [conflictClass === 'duplicate-member' ? 'duplicate-member-name' : 'duplicate-export-name'],
|
|
148
|
+
metadata: {
|
|
149
|
+
duplicateName: group[0].name,
|
|
150
|
+
containerKey: group[0].containerKey,
|
|
151
|
+
declarationIds: group.map((entry) => entry.id)
|
|
152
|
+
}
|
|
153
|
+
}, context);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function conflictSidecar(conflictClass, input, context) {
|
|
157
|
+
const defaults = classDefaults[conflictClass] ?? classDefaults['same-region'];
|
|
158
|
+
const affected = normalizeAffected(input.affected ?? input, context);
|
|
159
|
+
const reasonCodes = uniqueStrings([
|
|
160
|
+
...defaults.reasonCodes,
|
|
161
|
+
...strings(input.reasonCode),
|
|
162
|
+
...strings(input.reasonCodes)
|
|
163
|
+
]);
|
|
164
|
+
const severity = normalizeSeverity(input.severity) ?? defaults.severity;
|
|
165
|
+
const risk = normalizeRisk(input.risk) ?? defaults.risk;
|
|
166
|
+
const readiness = normalizeSemanticMergeReadiness(input.readiness) ?? defaults.readiness;
|
|
167
|
+
const suggestedOutcome = input.suggestedOutcome ?? input.outcome ?? defaults.suggestedOutcome;
|
|
168
|
+
const id = input.id ?? `js_ts_conflict_${idFragment(conflictClass)}_${idFragment([
|
|
169
|
+
...affected.keys,
|
|
170
|
+
...affected.sourcePaths,
|
|
171
|
+
...reasonCodes
|
|
172
|
+
].join('_'))}`;
|
|
173
|
+
return {
|
|
174
|
+
kind: 'frontier.lang.jsTsSemanticMergeConflictSidecar',
|
|
175
|
+
version: 1,
|
|
176
|
+
schema: 'frontier.lang.jsTsSemanticMergeConflictSidecar.v1',
|
|
177
|
+
id,
|
|
178
|
+
class: conflictClass,
|
|
179
|
+
severity,
|
|
180
|
+
risk,
|
|
181
|
+
readiness,
|
|
182
|
+
affected,
|
|
183
|
+
reasonCodes,
|
|
184
|
+
suggestedOutcome,
|
|
185
|
+
explanation: {
|
|
186
|
+
class: conflictClass,
|
|
187
|
+
severity,
|
|
188
|
+
risk,
|
|
189
|
+
affected,
|
|
190
|
+
reasonCodes,
|
|
191
|
+
suggestedOutcome
|
|
192
|
+
},
|
|
193
|
+
metadata: compactRecord(input.metadata)
|
|
194
|
+
};
|
|
195
|
+
}
|