@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 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, stable anchors, and source replay. The
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 baseKeys = new Set(baseIndex.orderedKeys);
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(entry.key);
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(entry.key, additions);
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
- baseKeys
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
- if (!sameImportShape(baseImport, variantImport)) {
95
+ const expansion = classifyImportExpansion(baseImport, variantImport);
96
+ if (!expansion.compatible) {
82
97
  addConflict(context, {
83
- code: baseImport.sideEffectOnly ? JsTsSafeMergeConflictCodes.sideEffectImportReorder : JsTsSafeMergeConflictCodes.importShapeChanged,
84
- gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
98
+ code: expansion.code,
99
+ gateId: expansion.gateId,
85
100
  side,
86
- message: `${side} source changes an existing import shape instead of adding named specifiers.`,
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 = head.entries.find((entry) => entry.key === baseEntry.key);
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(baseEntry.importInfo, mergedImportSpecifiers(baseEntry.importInfo.specifiers, workerAdditions, headAdditions))
35
+ text: renderImportStatement(importInfo, importInfo.specifiers)
30
36
  });
31
37
  }
32
38
 
33
- const importInsertionGroups = variantInsertionGroups(worker, workerPlan.baseKeys, 'worker', context, {
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.baseKeys, 'head', context, {
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 = headEntriesByKey.get(group.anchorKey);
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.baseKeys, 'worker', context, {
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.baseKeys, 'head', context, {
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 = headEntriesByKey.get(group.anchorKey);
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.key, entries }
136
- : { mode: 'before', anchorKey: nextBase.key, entries });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.130",
3
+ "version": "0.2.131",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",