@shapeshift-labs/frontier-lang-compiler 0.2.128 → 0.2.130
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 +1 -1
- package/dist/declarations/js-ts-safe-merge.d.ts +1 -0
- package/dist/js-ts-safe-merge-analyze.js +30 -32
- package/dist/js-ts-safe-merge-composed.js +1 -0
- package/dist/js-ts-safe-merge-context.js +1 -0
- package/dist/js-ts-safe-merge-import-entry-utils.js +40 -0
- package/dist/js-ts-safe-merge-new-import-validation.js +50 -0
- package/dist/js-ts-safe-merge-plan.js +86 -8
- package/dist/js-ts-safe-merge-semantic-artifact-ledger.js +16 -5
- package/dist/js-ts-safe-merge.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -170,7 +170,7 @@ 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
172
|
and rejected unsafe cases such as stale
|
|
173
|
-
ledger spans, import specifier
|
|
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
|
|
176
176
|
reason-code or gate values so distributed swarm evidence can point at a stable
|
|
@@ -102,6 +102,7 @@ export interface JsTsSafeMergeAdmission {
|
|
|
102
102
|
|
|
103
103
|
export interface JsTsSafeMergeSummary {
|
|
104
104
|
readonly importSpecifierAdditions: number;
|
|
105
|
+
readonly importDeclarationAdditions: number;
|
|
105
106
|
readonly topLevelDeclarationAdditions: number;
|
|
106
107
|
readonly changedExistingDeclarations: number;
|
|
107
108
|
readonly conflicts: number;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds } from './js-ts-safe-merge-constants.js';
|
|
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
|
+
import { importEntryBindings, mergedNewImportBindings } from './js-ts-safe-merge-import-entry-utils.js';
|
|
5
|
+
import { validateNewImportDeclarations } from './js-ts-safe-merge-new-import-validation.js';
|
|
4
6
|
|
|
5
7
|
export function analyzeVariantLedger(base, variant, baseIndex, side, context) {
|
|
6
8
|
const projectedBaseKeys = [];
|
|
7
9
|
const addedEntries = [];
|
|
10
|
+
const newImportEntries = [];
|
|
8
11
|
const importAdditions = new Map();
|
|
9
12
|
const baseKeys = new Set(baseIndex.orderedKeys);
|
|
10
13
|
|
|
@@ -12,13 +15,7 @@ export function analyzeVariantLedger(base, variant, baseIndex, side, context) {
|
|
|
12
15
|
const baseEntry = baseIndex.entriesByKey.get(entry.key);
|
|
13
16
|
if (!baseEntry) {
|
|
14
17
|
if (entry.kind === 'import') {
|
|
15
|
-
|
|
16
|
-
code: JsTsSafeMergeConflictCodes.newImportDeclaration,
|
|
17
|
-
gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
|
|
18
|
-
side,
|
|
19
|
-
message: `${side} source adds a new import declaration; only specifier additions to existing imports are accepted.`,
|
|
20
|
-
details: { key: entry.key, statement: entry.text.trim() }
|
|
21
|
-
});
|
|
18
|
+
newImportEntries.push(entry);
|
|
22
19
|
} else {
|
|
23
20
|
addedEntries.push(entry);
|
|
24
21
|
}
|
|
@@ -72,6 +69,7 @@ export function analyzeVariantLedger(base, variant, baseIndex, side, context) {
|
|
|
72
69
|
return {
|
|
73
70
|
side,
|
|
74
71
|
addedEntries,
|
|
72
|
+
newImportEntries,
|
|
75
73
|
importAdditions,
|
|
76
74
|
baseKeys
|
|
77
75
|
};
|
|
@@ -105,34 +103,27 @@ function analyzeImportStatementChange(baseEntry, variantEntry, side, context) {
|
|
|
105
103
|
|
|
106
104
|
const baseSpecifiers = baseImport.specifiers;
|
|
107
105
|
const variantSpecifiers = variantImport.specifiers;
|
|
108
|
-
|
|
106
|
+
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) {
|
|
109
110
|
addConflict(context, {
|
|
110
111
|
code: JsTsSafeMergeConflictCodes.importSpecifierRemoved,
|
|
111
112
|
gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
|
|
112
113
|
side,
|
|
113
114
|
message: `${side} source removes import specifiers.`,
|
|
114
|
-
details: {
|
|
115
|
+
details: {
|
|
116
|
+
key: baseEntry.key,
|
|
117
|
+
missing: missingBaseSpecifiers.map((specifier) => specifier.canonical)
|
|
118
|
+
}
|
|
115
119
|
});
|
|
116
120
|
return [];
|
|
117
121
|
}
|
|
118
|
-
|
|
119
|
-
if (variantSpecifiers[index]?.canonical !== baseSpecifiers[index].canonical) {
|
|
120
|
-
addConflict(context, {
|
|
121
|
-
code: JsTsSafeMergeConflictCodes.importSpecifierReordered,
|
|
122
|
-
gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
|
|
123
|
-
side,
|
|
124
|
-
message: `${side} source reorders or changes existing import specifiers.`,
|
|
125
|
-
details: {
|
|
126
|
-
key: baseEntry.key,
|
|
127
|
-
expected: baseSpecifiers.map((specifier) => specifier.canonical),
|
|
128
|
-
actual: variantSpecifiers.map((specifier) => specifier.canonical)
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const additions = variantSpecifiers.slice(baseSpecifiers.length);
|
|
122
|
+
const additions = variantSpecifiers.filter((specifier) => !baseSpecifiersByCanonical.has(specifier.canonical));
|
|
135
123
|
if (additions.length === 0 && !sameStatementText(baseEntry.text, variantEntry.text)) {
|
|
124
|
+
const baseCanonicalOrder = baseSpecifiers.map((specifier) => specifier.canonical);
|
|
125
|
+
const variantCanonicalOrder = variantSpecifiers.map((specifier) => specifier.canonical);
|
|
126
|
+
if (!arraysEqual(baseCanonicalOrder, variantCanonicalOrder)) return [];
|
|
136
127
|
addConflict(context, {
|
|
137
128
|
code: JsTsSafeMergeConflictCodes.importFormattingChanged,
|
|
138
129
|
gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
|
|
@@ -159,6 +150,7 @@ export function validateIndependentAdditions(base, workerPlan, headPlan, context
|
|
|
159
150
|
}
|
|
160
151
|
validateAddedEntryNames(workerPlan, declarationNames, context);
|
|
161
152
|
validateAddedEntryNames(headPlan, declarationNames, context);
|
|
153
|
+
validateNewImportDeclarations(workerPlan, headPlan, context);
|
|
162
154
|
validateCrossSideAddedNames(workerPlan, headPlan, context);
|
|
163
155
|
validateCrossSideExportStarAdditions(workerPlan, headPlan, context);
|
|
164
156
|
validateCrossSideImportAdditions(workerPlan, headPlan, context);
|
|
@@ -264,12 +256,7 @@ function validateMergedImportAndDeclarationNames(base, workerPlan, headPlan, con
|
|
|
264
256
|
...(workerPlan.importAdditions.get(entry.key) ?? []),
|
|
265
257
|
...(headPlan.importAdditions.get(entry.key) ?? [])
|
|
266
258
|
];
|
|
267
|
-
for (const specifier of [
|
|
268
|
-
...(entry.importInfo.defaultLocalName ? [{ localName: entry.importInfo.defaultLocalName, canonical: `default:${entry.importInfo.defaultLocalName}` }] : []),
|
|
269
|
-
...(entry.importInfo.namespaceLocalName ? [{ localName: entry.importInfo.namespaceLocalName, canonical: `namespace:${entry.importInfo.namespaceLocalName}` }] : []),
|
|
270
|
-
...entry.importInfo.specifiers,
|
|
271
|
-
...additions
|
|
272
|
-
]) {
|
|
259
|
+
for (const specifier of [...importEntryBindings(entry), ...additions]) {
|
|
273
260
|
if (importLocalNames.has(specifier.localName)) {
|
|
274
261
|
addConflict(context, {
|
|
275
262
|
code: JsTsSafeMergeConflictCodes.duplicateName,
|
|
@@ -289,4 +276,15 @@ function validateMergedImportAndDeclarationNames(base, workerPlan, headPlan, con
|
|
|
289
276
|
importLocalNames.add(specifier.localName);
|
|
290
277
|
}
|
|
291
278
|
}
|
|
279
|
+
for (const specifier of mergedNewImportBindings(workerPlan, headPlan)) {
|
|
280
|
+
if (importLocalNames.has(specifier.localName) || topLevelBindingNames.has(specifier.localName)) {
|
|
281
|
+
addConflict(context, {
|
|
282
|
+
code: JsTsSafeMergeConflictCodes.duplicateName,
|
|
283
|
+
gateId: JsTsSafeMergeGateIds.uniqueNames,
|
|
284
|
+
message: 'Merged new imports would duplicate an existing binding name.',
|
|
285
|
+
details: { localName: specifier.localName, specifier: specifier.canonical, importKey: specifier.importKey }
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
importLocalNames.add(specifier.localName);
|
|
289
|
+
}
|
|
292
290
|
}
|
|
@@ -106,6 +106,7 @@ function composedBlockedResult(input, phase, result, memberAnalysis) {
|
|
|
106
106
|
},
|
|
107
107
|
summary: {
|
|
108
108
|
importSpecifierAdditions: 0,
|
|
109
|
+
importDeclarationAdditions: 0,
|
|
109
110
|
topLevelDeclarationAdditions: 0,
|
|
110
111
|
changedExistingDeclarations: result.summary?.changedExistingDeclarations ?? 0,
|
|
111
112
|
conflicts: result.conflicts?.length ?? result.summary?.conflicts ?? 0,
|
|
@@ -35,6 +35,7 @@ export function blockedResult(context, ledgers = {}) {
|
|
|
35
35
|
},
|
|
36
36
|
summary: {
|
|
37
37
|
importSpecifierAdditions: 0,
|
|
38
|
+
importDeclarationAdditions: 0,
|
|
38
39
|
topLevelDeclarationAdditions: 0,
|
|
39
40
|
changedExistingDeclarations: context.conflicts.filter((conflict) => conflict.code === JsTsSafeMergeConflictCodes.changedExistingDeclaration).length,
|
|
40
41
|
conflicts: context.conflicts.length,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { importSpecifierCanonical } from './js-ts-safe-merge-ledger.js';
|
|
2
|
+
|
|
3
|
+
export function importEntryBindings(entry) {
|
|
4
|
+
const importInfo = entry?.importInfo;
|
|
5
|
+
if (!importInfo || importInfo.sideEffectOnly) return [];
|
|
6
|
+
return [
|
|
7
|
+
...(importInfo.defaultLocalName ? [{
|
|
8
|
+
localName: importInfo.defaultLocalName,
|
|
9
|
+
importedName: 'default',
|
|
10
|
+
typeOnly: importInfo.typeOnly,
|
|
11
|
+
canonical: `default:${importInfo.defaultLocalName}`
|
|
12
|
+
}] : []),
|
|
13
|
+
...(importInfo.namespaceLocalName ? [{
|
|
14
|
+
localName: importInfo.namespaceLocalName,
|
|
15
|
+
importedName: '*',
|
|
16
|
+
typeOnly: importInfo.typeOnly,
|
|
17
|
+
canonical: `namespace:${importInfo.namespaceLocalName}`
|
|
18
|
+
}] : []),
|
|
19
|
+
...importInfo.specifiers.map((specifier) => ({
|
|
20
|
+
localName: specifier.localName,
|
|
21
|
+
importedName: specifier.importedName,
|
|
22
|
+
typeOnly: specifier.typeOnly,
|
|
23
|
+
canonical: importSpecifierCanonical(specifier)
|
|
24
|
+
}))
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function mergedNewImportBindings(workerPlan, headPlan) {
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const bindings = [];
|
|
31
|
+
for (const entry of [...(workerPlan.newImportEntries ?? []), ...(headPlan.newImportEntries ?? [])]) {
|
|
32
|
+
for (const binding of importEntryBindings(entry)) {
|
|
33
|
+
const key = `${entry.key}:${binding.canonical}`;
|
|
34
|
+
if (seen.has(key)) continue;
|
|
35
|
+
seen.add(key);
|
|
36
|
+
bindings.push({ ...binding, importKey: entry.key });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return bindings;
|
|
40
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds } from './js-ts-safe-merge-constants.js';
|
|
2
|
+
import { addConflict } from './js-ts-safe-merge-context.js';
|
|
3
|
+
import { mergedNewImportBindings } from './js-ts-safe-merge-import-entry-utils.js';
|
|
4
|
+
|
|
5
|
+
export function validateNewImportDeclarations(workerPlan, headPlan, context) {
|
|
6
|
+
validateNoNewSideEffectImports(workerPlan, context);
|
|
7
|
+
validateNoNewSideEffectImports(headPlan, context);
|
|
8
|
+
validateCrossSideNewImportBindings(workerPlan, headPlan, context);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function validateNoNewSideEffectImports(plan, context) {
|
|
12
|
+
for (const entry of plan.newImportEntries ?? []) {
|
|
13
|
+
if (!entry.importInfo?.sideEffectOnly) continue;
|
|
14
|
+
addConflict(context, {
|
|
15
|
+
code: JsTsSafeMergeConflictCodes.sideEffectImportReorder,
|
|
16
|
+
gateId: JsTsSafeMergeGateIds.preserveBaseOrder,
|
|
17
|
+
side: plan.side,
|
|
18
|
+
message: `${plan.side} source adds a side-effect import; new side-effect ordering requires human review.`,
|
|
19
|
+
details: {
|
|
20
|
+
reasonCode: 'new-side-effect-import',
|
|
21
|
+
key: entry.key,
|
|
22
|
+
statement: entry.text.trim()
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateCrossSideNewImportBindings(workerPlan, headPlan, context) {
|
|
29
|
+
const ownersByLocalName = new Map();
|
|
30
|
+
for (const binding of mergedNewImportBindings(workerPlan, headPlan)) {
|
|
31
|
+
const owner = ownersByLocalName.get(binding.localName);
|
|
32
|
+
if (!owner) {
|
|
33
|
+
ownersByLocalName.set(binding.localName, binding);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
addConflict(context, {
|
|
37
|
+
code: JsTsSafeMergeConflictCodes.duplicateName,
|
|
38
|
+
gateId: JsTsSafeMergeGateIds.uniqueNames,
|
|
39
|
+
side: 'worker',
|
|
40
|
+
message: 'Worker and head add new imports with duplicate local binding names.',
|
|
41
|
+
details: {
|
|
42
|
+
localName: binding.localName,
|
|
43
|
+
firstImportKey: owner.importKey,
|
|
44
|
+
secondImportKey: binding.importKey,
|
|
45
|
+
firstSpecifier: owner.canonical,
|
|
46
|
+
secondSpecifier: binding.canonical
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -5,6 +5,7 @@ import { importSpecifierCanonical } from './js-ts-safe-merge-ledger.js';
|
|
|
5
5
|
export function createSourceMergePlan(base, worker, head, workerPlan, headPlan, context) {
|
|
6
6
|
const edits = [];
|
|
7
7
|
let importSpecifierAdditions = 0;
|
|
8
|
+
let importDeclarationAdditions = 0;
|
|
8
9
|
for (const baseEntry of base.entries.filter((entry) => entry.kind === 'import')) {
|
|
9
10
|
const workerAdditions = workerPlan.importAdditions.get(baseEntry.key) ?? [];
|
|
10
11
|
const headAdditions = headPlan.importAdditions.get(baseEntry.key) ?? [];
|
|
@@ -29,10 +30,49 @@ export function createSourceMergePlan(base, worker, head, workerPlan, headPlan,
|
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const importInsertionGroups = variantInsertionGroups(worker, workerPlan.baseKeys, 'worker', context, {
|
|
34
|
+
includeEntry: (entry) => entry.kind === 'import',
|
|
35
|
+
label: 'import'
|
|
36
|
+
});
|
|
37
|
+
const headImportInsertionGroups = variantInsertionGroups(head, headPlan.baseKeys, 'head', context, {
|
|
38
|
+
includeEntry: (entry) => entry.kind === 'import',
|
|
39
|
+
label: 'import'
|
|
40
|
+
});
|
|
41
|
+
const headImportGroupsByAnchor = new Map(headImportInsertionGroups.map((group) => [insertionGroupKey(group), group]));
|
|
35
42
|
const headEntriesByKey = new Map(head.entries.map((entry) => [entry.key, entry]));
|
|
43
|
+
for (const group of importInsertionGroups) {
|
|
44
|
+
const anchor = headEntriesByKey.get(group.anchorKey);
|
|
45
|
+
if (!anchor) {
|
|
46
|
+
addConflict(context, {
|
|
47
|
+
code: JsTsSafeMergeConflictCodes.insertionAnchorMissing,
|
|
48
|
+
gateId: JsTsSafeMergeGateIds.resolvedInsertionAnchors,
|
|
49
|
+
side: 'head',
|
|
50
|
+
message: 'Head source is missing an import insertion anchor.',
|
|
51
|
+
details: { anchorKey: group.anchorKey, mode: group.mode }
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const headGroup = headImportGroupsByAnchor.get(insertionGroupKey(group));
|
|
56
|
+
const insertionSpan = declarationInsertionSpan(head.sourceText, anchor, group.mode, headGroup);
|
|
57
|
+
const entries = mergedImportEntries(group.entries, headGroup?.entries ?? []);
|
|
58
|
+
edits.push({
|
|
59
|
+
kind: 'insert-imports',
|
|
60
|
+
start: insertionSpan.start,
|
|
61
|
+
end: insertionSpan.end,
|
|
62
|
+
text: importInsertionText(entries, detectLineEnding(head.sourceText))
|
|
63
|
+
});
|
|
64
|
+
importDeclarationAdditions += group.entries.length;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const insertionGroups = variantInsertionGroups(worker, workerPlan.baseKeys, 'worker', context, {
|
|
68
|
+
includeEntry: (entry) => entry.kind !== 'import',
|
|
69
|
+
label: 'declaration'
|
|
70
|
+
});
|
|
71
|
+
const headInsertionGroups = variantInsertionGroups(head, headPlan.baseKeys, 'head', context, {
|
|
72
|
+
includeEntry: (entry) => entry.kind !== 'import',
|
|
73
|
+
label: 'declaration'
|
|
74
|
+
});
|
|
75
|
+
const headDeclarationGroupsByAnchor = new Map(headInsertionGroups.map((group) => [insertionGroupKey(group), group]));
|
|
36
76
|
let topLevelDeclarationAdditions = 0;
|
|
37
77
|
for (const group of insertionGroups) {
|
|
38
78
|
const anchor = headEntriesByKey.get(group.anchorKey);
|
|
@@ -46,7 +86,7 @@ export function createSourceMergePlan(base, worker, head, workerPlan, headPlan,
|
|
|
46
86
|
});
|
|
47
87
|
continue;
|
|
48
88
|
}
|
|
49
|
-
const headGroup =
|
|
89
|
+
const headGroup = headDeclarationGroupsByAnchor.get(insertionGroupKey(group));
|
|
50
90
|
const insertionSpan = declarationInsertionSpan(head.sourceText, anchor, group.mode, headGroup);
|
|
51
91
|
const entries = mergedDeclarationEntries(group.entries, headGroup?.entries ?? []);
|
|
52
92
|
edits.push({
|
|
@@ -61,21 +101,23 @@ export function createSourceMergePlan(base, worker, head, workerPlan, headPlan,
|
|
|
61
101
|
return {
|
|
62
102
|
edits,
|
|
63
103
|
importSpecifierAdditions,
|
|
104
|
+
importDeclarationAdditions,
|
|
64
105
|
topLevelDeclarationAdditions
|
|
65
106
|
};
|
|
66
107
|
}
|
|
67
108
|
|
|
68
|
-
function variantInsertionGroups(variant, baseKeys, side, context) {
|
|
109
|
+
function variantInsertionGroups(variant, baseKeys, side, context, options = {}) {
|
|
110
|
+
const includeEntry = options.includeEntry ?? (() => true);
|
|
69
111
|
const groups = [];
|
|
70
112
|
let index = 0;
|
|
71
113
|
while (index < variant.entries.length) {
|
|
72
114
|
const entry = variant.entries[index];
|
|
73
|
-
if (baseKeys.has(entry.key)) {
|
|
115
|
+
if (baseKeys.has(entry.key) || !includeEntry(entry)) {
|
|
74
116
|
index += 1;
|
|
75
117
|
continue;
|
|
76
118
|
}
|
|
77
119
|
const start = index;
|
|
78
|
-
while (index < variant.entries.length && !baseKeys.has(variant.entries[index].key)) index += 1;
|
|
120
|
+
while (index < variant.entries.length && !baseKeys.has(variant.entries[index].key) && includeEntry(variant.entries[index])) index += 1;
|
|
79
121
|
const entries = variant.entries.slice(start, index);
|
|
80
122
|
const previousBase = findPreviousBaseEntry(variant.entries, start, baseKeys);
|
|
81
123
|
const nextBase = findNextBaseEntry(variant.entries, index, baseKeys);
|
|
@@ -84,7 +126,7 @@ function variantInsertionGroups(variant, baseKeys, side, context) {
|
|
|
84
126
|
code: JsTsSafeMergeConflictCodes.ambiguousInsertionPoint,
|
|
85
127
|
gateId: JsTsSafeMergeGateIds.resolvedInsertionAnchors,
|
|
86
128
|
side,
|
|
87
|
-
message: `${side} additions have no stable base declaration or import anchor.`,
|
|
129
|
+
message: `${side} ${options.label ?? 'additions'} additions have no stable base declaration or import anchor.`,
|
|
88
130
|
details: { entries: entries.map((item) => item.names?.[0] ?? item.key) }
|
|
89
131
|
});
|
|
90
132
|
continue;
|
|
@@ -133,6 +175,7 @@ function mergedImportSpecifiers(baseSpecifiers, workerAdditions, headAdditions)
|
|
|
133
175
|
}
|
|
134
176
|
|
|
135
177
|
function renderImportStatement(importInfo, specifiers) {
|
|
178
|
+
if (importInfo.sideEffectOnly) return `import ${importInfo.quote}${importInfo.moduleSpecifier}${importInfo.quote};`;
|
|
136
179
|
const clause = [];
|
|
137
180
|
if (importInfo.defaultLocalName) clause.push(importInfo.defaultLocalName);
|
|
138
181
|
if (importInfo.namespaceLocalName) clause.push(`* as ${importInfo.namespaceLocalName}`);
|
|
@@ -141,6 +184,41 @@ function renderImportStatement(importInfo, specifiers) {
|
|
|
141
184
|
return `import ${importType}${clause.join(', ')} from ${importInfo.quote}${importInfo.moduleSpecifier}${importInfo.quote};`;
|
|
142
185
|
}
|
|
143
186
|
|
|
187
|
+
function importInsertionText(entries, lineEnding) {
|
|
188
|
+
return entries
|
|
189
|
+
.map((entry) => normalizeLineEndings(entry.text.trimEnd(), lineEnding))
|
|
190
|
+
.map((text) => text.endsWith(lineEnding) ? text : `${text}${lineEnding}`)
|
|
191
|
+
.join('');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function mergedImportEntries(workerEntries, headEntries) {
|
|
195
|
+
const entriesByKey = new Map();
|
|
196
|
+
for (const entry of [...headEntries, ...workerEntries]) {
|
|
197
|
+
const existing = entriesByKey.get(entry.key);
|
|
198
|
+
if (!existing) {
|
|
199
|
+
entriesByKey.set(entry.key, { ...entry, importInfo: { ...entry.importInfo, specifiers: [...entry.importInfo.specifiers] } });
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
existing.importInfo.specifiers = mergedNewImportSpecifiers(existing.importInfo.specifiers, entry.importInfo.specifiers);
|
|
203
|
+
existing.text = renderImportStatement(existing.importInfo, existing.importInfo.specifiers);
|
|
204
|
+
}
|
|
205
|
+
return [...entriesByKey.values()].sort(compareImportEntries);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function mergedNewImportSpecifiers(left, right) {
|
|
209
|
+
const byCanonical = new Map();
|
|
210
|
+
for (const specifier of [...left, ...right]) byCanonical.set(specifier.canonical, specifier);
|
|
211
|
+
return [...byCanonical.values()].sort((a, b) => a.canonical.localeCompare(b.canonical));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function compareImportEntries(left, right) {
|
|
215
|
+
const moduleOrder = left.importInfo.moduleSpecifier.localeCompare(right.importInfo.moduleSpecifier);
|
|
216
|
+
if (moduleOrder !== 0) return moduleOrder;
|
|
217
|
+
const typeOrder = Number(Boolean(left.importInfo.typeOnly)) - Number(Boolean(right.importInfo.typeOnly));
|
|
218
|
+
if (typeOrder !== 0) return typeOrder;
|
|
219
|
+
return left.key.localeCompare(right.key);
|
|
220
|
+
}
|
|
221
|
+
|
|
144
222
|
function declarationInsertionText(entries, lineEnding) {
|
|
145
223
|
return entries
|
|
146
224
|
.map((entry) => normalizeLineEndings(entry.text.trimEnd(), lineEnding))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { importEntryBindings } from './js-ts-safe-merge-import-entry-utils.js';
|
|
4
5
|
|
|
5
6
|
function createOperationsFromLedgers(context) {
|
|
6
7
|
const headByKey = new Map(context.head.entries.map((entry) => [entry.key, entry]));
|
|
@@ -108,8 +109,8 @@ function insertionAnchorForMergedEntry(entry, index, context) {
|
|
|
108
109
|
if (previous) return {
|
|
109
110
|
mode: 'after',
|
|
110
111
|
anchorKey: previous.key,
|
|
111
|
-
anchorSymbolName:
|
|
112
|
-
anchorSymbolKind:
|
|
112
|
+
anchorSymbolName: insertionAnchorName(previous),
|
|
113
|
+
anchorSymbolKind: insertionAnchorKind(previous),
|
|
113
114
|
headSpan: spanForEntry(previous),
|
|
114
115
|
sourcePath: context.sourcePath
|
|
115
116
|
};
|
|
@@ -117,8 +118,8 @@ function insertionAnchorForMergedEntry(entry, index, context) {
|
|
|
117
118
|
if (next) return {
|
|
118
119
|
mode: 'before',
|
|
119
120
|
anchorKey: next.key,
|
|
120
|
-
anchorSymbolName:
|
|
121
|
-
anchorSymbolKind:
|
|
121
|
+
anchorSymbolName: insertionAnchorName(next),
|
|
122
|
+
anchorSymbolKind: insertionAnchorKind(next),
|
|
122
123
|
headSpan: spanForEntry(next),
|
|
123
124
|
sourcePath: context.sourcePath
|
|
124
125
|
};
|
|
@@ -162,7 +163,7 @@ function entryAnchor(entry, sourcePath, language) {
|
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
function entryName(entry) {
|
|
165
|
-
if (entry.kind === 'import') return entry.importInfo?.moduleSpecifier;
|
|
166
|
+
if (entry.kind === 'import') return importEntryBindings(entry)[0]?.localName ?? entry.importInfo?.moduleSpecifier;
|
|
166
167
|
return entry.names?.join('|') ?? entry.key;
|
|
167
168
|
}
|
|
168
169
|
|
|
@@ -171,6 +172,16 @@ function entrySymbolKind(entry) {
|
|
|
171
172
|
return entry.declarationInfo?.declarationKind ?? entry.kind;
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
function insertionAnchorName(entry) {
|
|
176
|
+
if (entry.kind === 'import') return entry.importInfo?.moduleSpecifier;
|
|
177
|
+
return entryName(entry);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function insertionAnchorKind(entry) {
|
|
181
|
+
if (entry.kind === 'import') return 'module';
|
|
182
|
+
return entrySymbolKind(entry);
|
|
183
|
+
}
|
|
184
|
+
|
|
174
185
|
function spanForEntry(entry) {
|
|
175
186
|
return { start: entry.start, end: entry.end };
|
|
176
187
|
}
|
package/dist/js-ts-safe-merge.js
CHANGED
|
@@ -85,6 +85,7 @@ export function safeMergeJsTsImportsAndDeclarations(input = {}) {
|
|
|
85
85
|
},
|
|
86
86
|
summary: {
|
|
87
87
|
importSpecifierAdditions: mergePlan.importSpecifierAdditions,
|
|
88
|
+
importDeclarationAdditions: mergePlan.importDeclarationAdditions,
|
|
88
89
|
topLevelDeclarationAdditions: mergePlan.topLevelDeclarationAdditions,
|
|
89
90
|
changedExistingDeclarations: 0,
|
|
90
91
|
conflicts: 0,
|
package/package.json
CHANGED