@shapeshift-labs/frontier-lang-compiler 0.2.130 → 0.2.131
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 +3 -2
- package/dist/js-ts-safe-merge-analyze.js +27 -33
- package/dist/js-ts-safe-merge-import-shape.js +102 -0
- package/dist/js-ts-safe-merge-parse-declarations.js +9 -0
- package/dist/js-ts-safe-merge-plan.js +48 -12
- package/dist/js-ts-safe-merge-semantic-artifact-ledger.js +7 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -169,7 +169,7 @@ The JS/TS semantic merge smoke corpus lives at
|
|
|
169
169
|
small and dependency-free. They cover accepted projection/replay cases, exact
|
|
170
170
|
source preservation, generated/source-map boundaries, safe import/declaration
|
|
171
171
|
merges, safe unordered member merges, composed top-level/member safe merges,
|
|
172
|
-
and rejected unsafe cases such as stale
|
|
172
|
+
existing import binding-shape additions, and rejected unsafe cases such as stale
|
|
173
173
|
ledger spans, import specifier removals, computed keys, duplicate exported
|
|
174
174
|
names, duplicate object members, decorators, overload anchors, and same-anchor
|
|
175
175
|
edit conflicts. Fixture failures include the fixture id and the actual
|
|
@@ -182,7 +182,8 @@ JS/TS ledger-approved head-to-merged source edits into a semantic edit script,
|
|
|
182
182
|
projection, replay, and already-applied replay. This is intentionally different
|
|
183
183
|
from asking the generic three-way edit classifier to bless every JS/TS case:
|
|
184
184
|
simultaneous import specifier additions are safe only because the JS/TS ledger
|
|
185
|
-
gates proved independent additions,
|
|
185
|
+
gates proved independent additions, compatible import binding expansions,
|
|
186
|
+
stable anchors, and source replay. The
|
|
186
187
|
artifacts keep `autoMergeClaim: false` and `semanticEquivalenceClaim: false`,
|
|
187
188
|
but give coordinators machine-readable proof that the projected source matches
|
|
188
189
|
the merge output and that applying the same projection again is a no-op.
|
|
@@ -2,17 +2,27 @@ import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds } from './js-ts-safe-m
|
|
|
2
2
|
import { addConflict, arraysEqual, sameStatementText } from './js-ts-safe-merge-context.js';
|
|
3
3
|
import { validateCrossSideExportStarAdditions } from './js-ts-safe-merge-export-star-validation.js';
|
|
4
4
|
import { importEntryBindings, mergedNewImportBindings } from './js-ts-safe-merge-import-entry-utils.js';
|
|
5
|
+
import {
|
|
6
|
+
classifyImportExpansion,
|
|
7
|
+
findCompatibleBaseImportEntry,
|
|
8
|
+
findSameImportTargetBaseEntry
|
|
9
|
+
} from './js-ts-safe-merge-import-shape.js';
|
|
5
10
|
import { validateNewImportDeclarations } from './js-ts-safe-merge-new-import-validation.js';
|
|
6
11
|
|
|
7
12
|
export function analyzeVariantLedger(base, variant, baseIndex, side, context) {
|
|
8
13
|
const projectedBaseKeys = [];
|
|
14
|
+
const projectedBaseKeySet = new Set();
|
|
15
|
+
const matchedVariantKeys = new Set();
|
|
16
|
+
const baseKeyByVariantKey = new Map();
|
|
9
17
|
const addedEntries = [];
|
|
10
18
|
const newImportEntries = [];
|
|
11
19
|
const importAdditions = new Map();
|
|
12
|
-
const
|
|
20
|
+
const baseEntries = baseIndex.orderedKeys.map((key) => baseIndex.entriesByKey.get(key)).filter(Boolean);
|
|
13
21
|
|
|
14
22
|
for (const entry of variant.entries) {
|
|
15
|
-
const baseEntry = baseIndex.entriesByKey.get(entry.key)
|
|
23
|
+
const baseEntry = baseIndex.entriesByKey.get(entry.key)
|
|
24
|
+
?? findCompatibleBaseImportEntry(entry, baseEntries, projectedBaseKeySet)
|
|
25
|
+
?? findSameImportTargetBaseEntry(entry, baseEntries, projectedBaseKeySet);
|
|
16
26
|
if (!baseEntry) {
|
|
17
27
|
if (entry.kind === 'import') {
|
|
18
28
|
newImportEntries.push(entry);
|
|
@@ -21,7 +31,10 @@ export function analyzeVariantLedger(base, variant, baseIndex, side, context) {
|
|
|
21
31
|
}
|
|
22
32
|
continue;
|
|
23
33
|
}
|
|
24
|
-
projectedBaseKeys.push(
|
|
34
|
+
projectedBaseKeys.push(baseEntry.key);
|
|
35
|
+
projectedBaseKeySet.add(baseEntry.key);
|
|
36
|
+
matchedVariantKeys.add(entry.key);
|
|
37
|
+
baseKeyByVariantKey.set(entry.key, baseEntry.key);
|
|
25
38
|
if (entry.kind !== baseEntry.kind) {
|
|
26
39
|
addConflict(context, {
|
|
27
40
|
code: JsTsSafeMergeConflictCodes.parserLedgerLoss,
|
|
@@ -34,7 +47,7 @@ export function analyzeVariantLedger(base, variant, baseIndex, side, context) {
|
|
|
34
47
|
}
|
|
35
48
|
if (entry.kind === 'import') {
|
|
36
49
|
const additions = analyzeImportStatementChange(baseEntry, entry, side, context);
|
|
37
|
-
if (additions.length) importAdditions.set(
|
|
50
|
+
if (additions.length) importAdditions.set(baseEntry.key, additions);
|
|
38
51
|
} else if (!sameStatementText(baseEntry.text, entry.text)) {
|
|
39
52
|
const typeAliasConflict = baseEntry.declarationInfo?.declarationKind === 'type'
|
|
40
53
|
|| entry.declarationInfo?.declarationKind === 'type';
|
|
@@ -71,20 +84,22 @@ export function analyzeVariantLedger(base, variant, baseIndex, side, context) {
|
|
|
71
84
|
addedEntries,
|
|
72
85
|
newImportEntries,
|
|
73
86
|
importAdditions,
|
|
74
|
-
|
|
87
|
+
matchedVariantKeys,
|
|
88
|
+
baseKeyByVariantKey
|
|
75
89
|
};
|
|
76
90
|
}
|
|
77
91
|
|
|
78
92
|
function analyzeImportStatementChange(baseEntry, variantEntry, side, context) {
|
|
79
93
|
const baseImport = baseEntry.importInfo;
|
|
80
94
|
const variantImport = variantEntry.importInfo;
|
|
81
|
-
|
|
95
|
+
const expansion = classifyImportExpansion(baseImport, variantImport);
|
|
96
|
+
if (!expansion.compatible) {
|
|
82
97
|
addConflict(context, {
|
|
83
|
-
code:
|
|
84
|
-
gateId:
|
|
98
|
+
code: expansion.code,
|
|
99
|
+
gateId: expansion.gateId,
|
|
85
100
|
side,
|
|
86
|
-
message:
|
|
87
|
-
details: { key: baseEntry.key }
|
|
101
|
+
message: expansion.message,
|
|
102
|
+
details: { key: baseEntry.key, ...expansion.details }
|
|
88
103
|
});
|
|
89
104
|
return [];
|
|
90
105
|
}
|
|
@@ -104,22 +119,9 @@ function analyzeImportStatementChange(baseEntry, variantEntry, side, context) {
|
|
|
104
119
|
const baseSpecifiers = baseImport.specifiers;
|
|
105
120
|
const variantSpecifiers = variantImport.specifiers;
|
|
106
121
|
const baseSpecifiersByCanonical = new Set(baseSpecifiers.map((specifier) => specifier.canonical));
|
|
107
|
-
const variantSpecifiersByCanonical = new Set(variantSpecifiers.map((specifier) => specifier.canonical));
|
|
108
|
-
const missingBaseSpecifiers = baseSpecifiers.filter((specifier) => !variantSpecifiersByCanonical.has(specifier.canonical));
|
|
109
|
-
if (missingBaseSpecifiers.length) {
|
|
110
|
-
addConflict(context, {
|
|
111
|
-
code: JsTsSafeMergeConflictCodes.importSpecifierRemoved,
|
|
112
|
-
gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
|
|
113
|
-
side,
|
|
114
|
-
message: `${side} source removes import specifiers.`,
|
|
115
|
-
details: {
|
|
116
|
-
key: baseEntry.key,
|
|
117
|
-
missing: missingBaseSpecifiers.map((specifier) => specifier.canonical)
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
return [];
|
|
121
|
-
}
|
|
122
122
|
const additions = variantSpecifiers.filter((specifier) => !baseSpecifiersByCanonical.has(specifier.canonical));
|
|
123
|
+
if (expansion.defaultAddition) additions.unshift(expansion.defaultAddition);
|
|
124
|
+
if (expansion.namespaceAddition) additions.unshift(expansion.namespaceAddition);
|
|
123
125
|
if (additions.length === 0 && !sameStatementText(baseEntry.text, variantEntry.text)) {
|
|
124
126
|
const baseCanonicalOrder = baseSpecifiers.map((specifier) => specifier.canonical);
|
|
125
127
|
const variantCanonicalOrder = variantSpecifiers.map((specifier) => specifier.canonical);
|
|
@@ -135,14 +137,6 @@ function analyzeImportStatementChange(baseEntry, variantEntry, side, context) {
|
|
|
135
137
|
return additions;
|
|
136
138
|
}
|
|
137
139
|
|
|
138
|
-
function sameImportShape(left, right) {
|
|
139
|
-
return left.moduleSpecifier === right.moduleSpecifier
|
|
140
|
-
&& left.typeOnly === right.typeOnly
|
|
141
|
-
&& left.sideEffectOnly === right.sideEffectOnly
|
|
142
|
-
&& left.defaultLocalName === right.defaultLocalName
|
|
143
|
-
&& left.namespaceLocalName === right.namespaceLocalName;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
140
|
export function validateIndependentAdditions(base, workerPlan, headPlan, context) {
|
|
147
141
|
const declarationNames = new Set();
|
|
148
142
|
for (const entry of base.entries.filter((item) => item.kind !== 'import')) {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds } from './js-ts-safe-merge-constants.js';
|
|
2
|
+
|
|
3
|
+
function classifyImportExpansion(baseImport, variantImport) {
|
|
4
|
+
if (baseImport.moduleSpecifier !== variantImport.moduleSpecifier
|
|
5
|
+
|| baseImport.typeOnly !== variantImport.typeOnly
|
|
6
|
+
|| baseImport.sideEffectOnly !== variantImport.sideEffectOnly) {
|
|
7
|
+
return importShapeConflict('variant changes import module, type-only mode, or side-effect mode');
|
|
8
|
+
}
|
|
9
|
+
if (baseImport.sideEffectOnly) return { compatible: true };
|
|
10
|
+
if (baseImport.defaultLocalName && variantImport.defaultLocalName !== baseImport.defaultLocalName) {
|
|
11
|
+
return importShapeConflict('variant changes or removes an existing default import binding');
|
|
12
|
+
}
|
|
13
|
+
if (baseImport.namespaceLocalName && variantImport.namespaceLocalName !== baseImport.namespaceLocalName) {
|
|
14
|
+
return importShapeConflict('variant changes or removes an existing namespace import binding');
|
|
15
|
+
}
|
|
16
|
+
if (variantImport.namespaceLocalName && variantImport.specifiers.length > 0) {
|
|
17
|
+
return importShapeConflict('variant combines namespace and named import specifiers');
|
|
18
|
+
}
|
|
19
|
+
const variantSpecifiersByCanonical = new Set(variantImport.specifiers.map((specifier) => specifier.canonical));
|
|
20
|
+
const missingBaseSpecifiers = baseImport.specifiers.filter((specifier) => !variantSpecifiersByCanonical.has(specifier.canonical));
|
|
21
|
+
if (missingBaseSpecifiers.length) {
|
|
22
|
+
return {
|
|
23
|
+
compatible: false,
|
|
24
|
+
code: JsTsSafeMergeConflictCodes.importSpecifierRemoved,
|
|
25
|
+
gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
|
|
26
|
+
message: 'variant removes import specifiers.',
|
|
27
|
+
details: { missing: missingBaseSpecifiers.map((specifier) => specifier.canonical) }
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
compatible: true,
|
|
32
|
+
defaultAddition: !baseImport.defaultLocalName && variantImport.defaultLocalName
|
|
33
|
+
? importShapeAddition('default', variantImport.defaultLocalName, variantImport.typeOnly)
|
|
34
|
+
: undefined,
|
|
35
|
+
namespaceAddition: !baseImport.namespaceLocalName && variantImport.namespaceLocalName
|
|
36
|
+
? importShapeAddition('namespace', variantImport.namespaceLocalName, variantImport.typeOnly)
|
|
37
|
+
: undefined
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function findCompatibleBaseImportEntry(variantEntry, baseEntries, usedBaseKeys = new Set()) {
|
|
42
|
+
if (variantEntry?.kind !== 'import') return undefined;
|
|
43
|
+
const candidates = [];
|
|
44
|
+
for (const baseEntry of baseEntries) {
|
|
45
|
+
if (usedBaseKeys.has(baseEntry.key) || baseEntry.kind !== 'import') continue;
|
|
46
|
+
if (classifyImportExpansion(baseEntry.importInfo, variantEntry.importInfo).compatible) candidates.push(baseEntry);
|
|
47
|
+
}
|
|
48
|
+
return candidates.length === 1 ? candidates[0] : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function findSameImportTargetBaseEntry(variantEntry, baseEntries, usedBaseKeys = new Set()) {
|
|
52
|
+
if (variantEntry?.kind !== 'import') return undefined;
|
|
53
|
+
const candidates = [];
|
|
54
|
+
for (const baseEntry of baseEntries) {
|
|
55
|
+
if (usedBaseKeys.has(baseEntry.key) || baseEntry.kind !== 'import') continue;
|
|
56
|
+
if (sameImportTarget(baseEntry.importInfo, variantEntry.importInfo)) candidates.push(baseEntry);
|
|
57
|
+
}
|
|
58
|
+
return candidates.length === 1 ? candidates[0] : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function findCompatibleVariantImportEntry(baseEntry, variantEntries, usedVariantKeys = new Set()) {
|
|
62
|
+
if (baseEntry?.kind !== 'import') return undefined;
|
|
63
|
+
const candidates = [];
|
|
64
|
+
for (const variantEntry of variantEntries) {
|
|
65
|
+
if (usedVariantKeys.has(variantEntry.key) || variantEntry.kind !== 'import') continue;
|
|
66
|
+
if (classifyImportExpansion(baseEntry.importInfo, variantEntry.importInfo).compatible) candidates.push(variantEntry);
|
|
67
|
+
}
|
|
68
|
+
return candidates.length === 1 ? candidates[0] : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sameImportTarget(left, right) {
|
|
72
|
+
return left.moduleSpecifier === right.moduleSpecifier
|
|
73
|
+
&& left.typeOnly === right.typeOnly
|
|
74
|
+
&& left.sideEffectOnly === right.sideEffectOnly;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function importShapeConflict(reasonCode) {
|
|
78
|
+
return {
|
|
79
|
+
compatible: false,
|
|
80
|
+
code: JsTsSafeMergeConflictCodes.importShapeChanged,
|
|
81
|
+
gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
|
|
82
|
+
message: 'variant changes an existing import shape instead of adding safe bindings.',
|
|
83
|
+
details: { reasonCode }
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function importShapeAddition(kind, localName, typeOnly) {
|
|
88
|
+
return {
|
|
89
|
+
additionKind: kind,
|
|
90
|
+
importedName: kind === 'default' ? 'default' : '*',
|
|
91
|
+
localName,
|
|
92
|
+
typeOnly,
|
|
93
|
+
canonical: `${kind}:${localName}`
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
classifyImportExpansion,
|
|
99
|
+
findCompatibleBaseImportEntry,
|
|
100
|
+
findSameImportTargetBaseEntry,
|
|
101
|
+
findCompatibleVariantImportEntry
|
|
102
|
+
};
|
|
@@ -61,6 +61,15 @@ function parseImportInfo(text) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
function parseImportClause(clause, options) {
|
|
64
|
+
const defaultAndNamespace = clause.match(/^([A-Za-z_$][\w$]*)\s*,\s*\*\s+as\s+([A-Za-z_$][\w$]*)$/);
|
|
65
|
+
if (defaultAndNamespace) {
|
|
66
|
+
return {
|
|
67
|
+
defaultLocalName: defaultAndNamespace[1],
|
|
68
|
+
namespaceLocalName: defaultAndNamespace[2],
|
|
69
|
+
specifiers: []
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
64
73
|
const namespace = clause.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)$/);
|
|
65
74
|
if (namespace) {
|
|
66
75
|
return {
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds } from './js-ts-safe-merge-constants.js';
|
|
2
2
|
import { addConflict, detectLineEnding, normalizeLineEndings } from './js-ts-safe-merge-context.js';
|
|
3
|
+
import { findCompatibleVariantImportEntry } from './js-ts-safe-merge-import-shape.js';
|
|
3
4
|
import { importSpecifierCanonical } from './js-ts-safe-merge-ledger.js';
|
|
4
5
|
|
|
5
6
|
export function createSourceMergePlan(base, worker, head, workerPlan, headPlan, context) {
|
|
6
7
|
const edits = [];
|
|
7
8
|
let importSpecifierAdditions = 0;
|
|
8
9
|
let importDeclarationAdditions = 0;
|
|
10
|
+
const headEntriesByBaseKey = variantEntriesByBaseKey(head, headPlan);
|
|
11
|
+
const usedHeadImportKeys = new Set();
|
|
9
12
|
for (const baseEntry of base.entries.filter((entry) => entry.kind === 'import')) {
|
|
10
13
|
const workerAdditions = workerPlan.importAdditions.get(baseEntry.key) ?? [];
|
|
11
14
|
const headAdditions = headPlan.importAdditions.get(baseEntry.key) ?? [];
|
|
12
15
|
if (!workerAdditions.length && !headAdditions.length) continue;
|
|
13
16
|
importSpecifierAdditions += workerAdditions.length + headAdditions.length;
|
|
14
|
-
const headEntry =
|
|
17
|
+
const headEntry = headEntriesByBaseKey.get(baseEntry.key)
|
|
18
|
+
?? findCompatibleVariantImportEntry(baseEntry, head.entries, usedHeadImportKeys);
|
|
15
19
|
if (!headEntry) {
|
|
16
20
|
addConflict(context, {
|
|
17
21
|
code: JsTsSafeMergeConflictCodes.insertionAnchorMissing,
|
|
@@ -22,26 +26,29 @@ export function createSourceMergePlan(base, worker, head, workerPlan, headPlan,
|
|
|
22
26
|
});
|
|
23
27
|
continue;
|
|
24
28
|
}
|
|
29
|
+
usedHeadImportKeys.add(headEntry.key);
|
|
30
|
+
const importInfo = mergedImportInfo(baseEntry.importInfo, workerAdditions, headAdditions);
|
|
25
31
|
edits.push({
|
|
26
32
|
kind: 'replace-import',
|
|
27
33
|
start: headEntry.start,
|
|
28
34
|
end: headEntry.end,
|
|
29
|
-
text: renderImportStatement(
|
|
35
|
+
text: renderImportStatement(importInfo, importInfo.specifiers)
|
|
30
36
|
});
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
const importInsertionGroups = variantInsertionGroups(worker, workerPlan.
|
|
39
|
+
const importInsertionGroups = variantInsertionGroups(worker, workerPlan.matchedVariantKeys, 'worker', context, {
|
|
40
|
+
baseKeyByVariantKey: workerPlan.baseKeyByVariantKey,
|
|
34
41
|
includeEntry: (entry) => entry.kind === 'import',
|
|
35
42
|
label: 'import'
|
|
36
43
|
});
|
|
37
|
-
const headImportInsertionGroups = variantInsertionGroups(head, headPlan.
|
|
44
|
+
const headImportInsertionGroups = variantInsertionGroups(head, headPlan.matchedVariantKeys, 'head', context, {
|
|
45
|
+
baseKeyByVariantKey: headPlan.baseKeyByVariantKey,
|
|
38
46
|
includeEntry: (entry) => entry.kind === 'import',
|
|
39
47
|
label: 'import'
|
|
40
48
|
});
|
|
41
49
|
const headImportGroupsByAnchor = new Map(headImportInsertionGroups.map((group) => [insertionGroupKey(group), group]));
|
|
42
|
-
const headEntriesByKey = new Map(head.entries.map((entry) => [entry.key, entry]));
|
|
43
50
|
for (const group of importInsertionGroups) {
|
|
44
|
-
const anchor =
|
|
51
|
+
const anchor = headEntriesByBaseKey.get(group.anchorKey);
|
|
45
52
|
if (!anchor) {
|
|
46
53
|
addConflict(context, {
|
|
47
54
|
code: JsTsSafeMergeConflictCodes.insertionAnchorMissing,
|
|
@@ -64,18 +71,20 @@ export function createSourceMergePlan(base, worker, head, workerPlan, headPlan,
|
|
|
64
71
|
importDeclarationAdditions += group.entries.length;
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
const insertionGroups = variantInsertionGroups(worker, workerPlan.
|
|
74
|
+
const insertionGroups = variantInsertionGroups(worker, workerPlan.matchedVariantKeys, 'worker', context, {
|
|
75
|
+
baseKeyByVariantKey: workerPlan.baseKeyByVariantKey,
|
|
68
76
|
includeEntry: (entry) => entry.kind !== 'import',
|
|
69
77
|
label: 'declaration'
|
|
70
78
|
});
|
|
71
|
-
const headInsertionGroups = variantInsertionGroups(head, headPlan.
|
|
79
|
+
const headInsertionGroups = variantInsertionGroups(head, headPlan.matchedVariantKeys, 'head', context, {
|
|
80
|
+
baseKeyByVariantKey: headPlan.baseKeyByVariantKey,
|
|
72
81
|
includeEntry: (entry) => entry.kind !== 'import',
|
|
73
82
|
label: 'declaration'
|
|
74
83
|
});
|
|
75
84
|
const headDeclarationGroupsByAnchor = new Map(headInsertionGroups.map((group) => [insertionGroupKey(group), group]));
|
|
76
85
|
let topLevelDeclarationAdditions = 0;
|
|
77
86
|
for (const group of insertionGroups) {
|
|
78
|
-
const anchor =
|
|
87
|
+
const anchor = headEntriesByBaseKey.get(group.anchorKey);
|
|
79
88
|
if (!anchor) {
|
|
80
89
|
addConflict(context, {
|
|
81
90
|
code: JsTsSafeMergeConflictCodes.insertionAnchorMissing,
|
|
@@ -108,6 +117,7 @@ export function createSourceMergePlan(base, worker, head, workerPlan, headPlan,
|
|
|
108
117
|
|
|
109
118
|
function variantInsertionGroups(variant, baseKeys, side, context, options = {}) {
|
|
110
119
|
const includeEntry = options.includeEntry ?? (() => true);
|
|
120
|
+
const baseKeyByVariantKey = options.baseKeyByVariantKey ?? new Map();
|
|
111
121
|
const groups = [];
|
|
112
122
|
let index = 0;
|
|
113
123
|
while (index < variant.entries.length) {
|
|
@@ -132,12 +142,26 @@ function variantInsertionGroups(variant, baseKeys, side, context, options = {})
|
|
|
132
142
|
continue;
|
|
133
143
|
}
|
|
134
144
|
groups.push(previousBase
|
|
135
|
-
? { mode: 'after', anchorKey: previousBase
|
|
136
|
-
: { mode: 'before', anchorKey: nextBase
|
|
145
|
+
? { mode: 'after', anchorKey: baseKeyForVariantEntry(previousBase, baseKeyByVariantKey), entries }
|
|
146
|
+
: { mode: 'before', anchorKey: baseKeyForVariantEntry(nextBase, baseKeyByVariantKey), entries });
|
|
137
147
|
}
|
|
138
148
|
return groups;
|
|
139
149
|
}
|
|
140
150
|
|
|
151
|
+
function variantEntriesByBaseKey(variant, plan) {
|
|
152
|
+
const entriesByKey = new Map(variant.entries.map((entry) => [entry.key, entry]));
|
|
153
|
+
const entriesByBaseKey = new Map();
|
|
154
|
+
for (const [variantKey, baseKey] of plan.baseKeyByVariantKey ?? []) {
|
|
155
|
+
const entry = entriesByKey.get(variantKey);
|
|
156
|
+
if (entry) entriesByBaseKey.set(baseKey, entry);
|
|
157
|
+
}
|
|
158
|
+
return entriesByBaseKey;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function baseKeyForVariantEntry(entry, baseKeyByVariantKey) {
|
|
162
|
+
return baseKeyByVariantKey.get(entry.key) ?? entry.key;
|
|
163
|
+
}
|
|
164
|
+
|
|
141
165
|
function insertionGroupKey(group) {
|
|
142
166
|
return `${group.mode}:${group.anchorKey}`;
|
|
143
167
|
}
|
|
@@ -165,7 +189,7 @@ export function applySourceMergePlan(sourceText, mergePlan) {
|
|
|
165
189
|
function mergedImportSpecifiers(baseSpecifiers, workerAdditions, headAdditions) {
|
|
166
190
|
const seen = new Set(baseSpecifiers.map((specifier) => specifier.canonical));
|
|
167
191
|
const additions = [];
|
|
168
|
-
for (const specifier of [...workerAdditions, ...headAdditions]) {
|
|
192
|
+
for (const specifier of [...workerAdditions, ...headAdditions].filter((addition) => !addition.additionKind)) {
|
|
169
193
|
if (seen.has(specifier.canonical)) continue;
|
|
170
194
|
seen.add(specifier.canonical);
|
|
171
195
|
additions.push(specifier);
|
|
@@ -174,6 +198,18 @@ function mergedImportSpecifiers(baseSpecifiers, workerAdditions, headAdditions)
|
|
|
174
198
|
return [...baseSpecifiers, ...additions];
|
|
175
199
|
}
|
|
176
200
|
|
|
201
|
+
function mergedImportInfo(baseImportInfo, workerAdditions, headAdditions) {
|
|
202
|
+
const additions = [...workerAdditions, ...headAdditions];
|
|
203
|
+
const defaultAddition = additions.find((addition) => addition.additionKind === 'default');
|
|
204
|
+
const namespaceAddition = additions.find((addition) => addition.additionKind === 'namespace');
|
|
205
|
+
return {
|
|
206
|
+
...baseImportInfo,
|
|
207
|
+
defaultLocalName: baseImportInfo.defaultLocalName ?? defaultAddition?.localName,
|
|
208
|
+
namespaceLocalName: baseImportInfo.namespaceLocalName ?? namespaceAddition?.localName,
|
|
209
|
+
specifiers: mergedImportSpecifiers(baseImportInfo.specifiers, workerAdditions, headAdditions)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
177
213
|
function renderImportStatement(importInfo, specifiers) {
|
|
178
214
|
if (importInfo.sideEffectOnly) return `import ${importInfo.quote}${importInfo.moduleSpecifier}${importInfo.quote};`;
|
|
179
215
|
const clause = [];
|
|
@@ -2,12 +2,16 @@ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
|
2
2
|
import { idFragment } from './native-import-utils.js';
|
|
3
3
|
import { detectLineEnding, normalizeLineEndings } from './js-ts-safe-merge-context.js';
|
|
4
4
|
import { importEntryBindings } from './js-ts-safe-merge-import-entry-utils.js';
|
|
5
|
+
import { findCompatibleBaseImportEntry } from './js-ts-safe-merge-import-shape.js';
|
|
5
6
|
|
|
6
7
|
function createOperationsFromLedgers(context) {
|
|
7
8
|
const headByKey = new Map(context.head.entries.map((entry) => [entry.key, entry]));
|
|
9
|
+
const usedHeadKeys = new Set();
|
|
8
10
|
return context.merged.entries.flatMap((entry, index) => {
|
|
9
|
-
const headEntry = headByKey.get(entry.key)
|
|
11
|
+
const headEntry = headByKey.get(entry.key)
|
|
12
|
+
?? findCompatibleBaseImportEntry(entry, context.head.entries, usedHeadKeys);
|
|
10
13
|
if (headEntry) {
|
|
14
|
+
usedHeadKeys.add(headEntry.key);
|
|
11
15
|
if (sameEntryText(headEntry.text, entry.text)) return [];
|
|
12
16
|
return [operationRecord({
|
|
13
17
|
...context,
|
|
@@ -129,7 +133,8 @@ function insertionAnchorForMergedEntry(entry, index, context) {
|
|
|
129
133
|
function nearestHeadEntry(mergedEntries, headEntries, startIndex, step) {
|
|
130
134
|
const headByKey = new Map(headEntries.map((entry) => [entry.key, entry]));
|
|
131
135
|
for (let index = startIndex + step; index >= 0 && index < mergedEntries.length; index += step) {
|
|
132
|
-
const headEntry = headByKey.get(mergedEntries[index].key)
|
|
136
|
+
const headEntry = headByKey.get(mergedEntries[index].key)
|
|
137
|
+
?? findCompatibleBaseImportEntry(mergedEntries[index], headEntries);
|
|
133
138
|
if (headEntry) return headEntry;
|
|
134
139
|
}
|
|
135
140
|
return undefined;
|
package/package.json
CHANGED