@shapeshift-labs/frontier-lang-compiler 0.2.133 → 0.2.135
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 +6 -1
- package/dist/js-ts-safe-member-merge.js +10 -8
- package/dist/js-ts-safe-merge-composed.js +22 -5
- package/dist/js-ts-semantic-merge-member-source.js +40 -7
- package/dist/js-ts-semantic-merge-member-utils.js +5 -2
- package/dist/js-ts-semantic-merge-parse.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -194,7 +194,12 @@ generic semantic edit script path. The fallback admits the merge only after the
|
|
|
194
194
|
script is an auto-merge candidate, the source projection succeeds, replay on
|
|
195
195
|
current head is `accepted-clean`, and replay on the projected source is
|
|
196
196
|
`already-applied`. Same-anchor head edits, stale anchors, and non-body conflicts
|
|
197
|
-
remain blocked for review.
|
|
197
|
+
remain blocked for review. The same fallback composes with declared unordered
|
|
198
|
+
member-addition regions, so a verified body edit can still merge alongside safe
|
|
199
|
+
interface, type, class, or object member additions. Existing class/object method
|
|
200
|
+
or property body edits inside the declared member region are preserved for
|
|
201
|
+
semantic replay while added members are neutralized; object member additions are
|
|
202
|
+
re-emitted with safe commas when both sides add final properties.
|
|
198
203
|
|
|
199
204
|
Project-level JS/TS safe merges compose the same file-level gates across a
|
|
200
205
|
base/worker/head file set. They preserve head-only files, admit worker-only
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
normalizeKind,
|
|
7
7
|
normalizeMemberText,
|
|
8
8
|
parseMembers,
|
|
9
|
+
removePreparedMemberAdditions,
|
|
9
10
|
uniqueStrings
|
|
10
11
|
} from './js-ts-semantic-merge-parse.js';
|
|
11
12
|
import { mergeResult } from './js-ts-safe-member-merge-result.js';
|
|
@@ -43,7 +44,8 @@ function analyzeJsTsSafeMemberAdditions(input = {}) {
|
|
|
43
44
|
function neutralizeJsTsSafeMemberMergeSources(input = {}) {
|
|
44
45
|
const analysis = analyzeJsTsSafeMemberAdditions({
|
|
45
46
|
...input,
|
|
46
|
-
allowNonPolicySourceChanges: true
|
|
47
|
+
allowNonPolicySourceChanges: true,
|
|
48
|
+
allowExistingMemberChanges: true
|
|
47
49
|
});
|
|
48
50
|
if (analysis.reasonCodes.length) {
|
|
49
51
|
return {
|
|
@@ -56,8 +58,8 @@ function neutralizeJsTsSafeMemberMergeSources(input = {}) {
|
|
|
56
58
|
ok: true,
|
|
57
59
|
analysis,
|
|
58
60
|
baseSourceText: analysis.baseSourceText,
|
|
59
|
-
workerSourceText:
|
|
60
|
-
headSourceText:
|
|
61
|
+
workerSourceText: removePreparedMemberAdditions(analysis.workerSourceText, analysis.preparedRegions, 'worker'),
|
|
62
|
+
headSourceText: removePreparedMemberAdditions(analysis.headSourceText, analysis.preparedRegions, 'head')
|
|
61
63
|
};
|
|
62
64
|
}
|
|
63
65
|
|
|
@@ -81,7 +83,7 @@ function prepareJsTsSafeMemberAdditions(input = {}) {
|
|
|
81
83
|
reasonCodes.push(...policyReasons);
|
|
82
84
|
if (policyReasons.length) continue;
|
|
83
85
|
if (typeof baseSourceText !== 'string' || typeof workerSourceText !== 'string' || typeof headSourceText !== 'string') continue;
|
|
84
|
-
const prepared = prepareRegion({ region, baseSourceText, workerSourceText, headSourceText });
|
|
86
|
+
const prepared = prepareRegion({ region, baseSourceText, workerSourceText, headSourceText, allowExistingMemberChanges: input.allowExistingMemberChanges });
|
|
85
87
|
reasonCodes.push(...prepared.reasonCodes);
|
|
86
88
|
if (prepared.ok) preparedRegions.push(prepared.value);
|
|
87
89
|
}
|
|
@@ -163,8 +165,8 @@ function prepareRegion(input) {
|
|
|
163
165
|
const baseByKey = membersByKey(baseMembers.members);
|
|
164
166
|
const workerByKey = membersByKey(workerMembers.members);
|
|
165
167
|
const headByKey = membersByKey(headMembers.members);
|
|
166
|
-
reasonCodes.push(...existingMemberReasons(input.region, 'worker', baseMembers.members, workerMembers.members, workerByKey));
|
|
167
|
-
reasonCodes.push(...existingMemberReasons(input.region, 'head', baseMembers.members, headMembers.members, headByKey));
|
|
168
|
+
reasonCodes.push(...existingMemberReasons(input.region, 'worker', baseMembers.members, workerMembers.members, workerByKey, input.allowExistingMemberChanges));
|
|
169
|
+
reasonCodes.push(...existingMemberReasons(input.region, 'head', baseMembers.members, headMembers.members, headByKey, input.allowExistingMemberChanges));
|
|
168
170
|
const workerAddedKeys = workerMembers.members.map((member) => member.key).filter((key) => !baseByKey.has(key));
|
|
169
171
|
const headAddedKeys = headMembers.members.map((member) => member.key).filter((key) => !baseByKey.has(key));
|
|
170
172
|
reasonCodes.push(...duplicateAddedReasons(input.region, workerAddedKeys, workerByKey, headByKey));
|
|
@@ -212,7 +214,7 @@ function duplicateReasons(region, side, members) {
|
|
|
212
214
|
});
|
|
213
215
|
}
|
|
214
216
|
|
|
215
|
-
function existingMemberReasons(region, side, baseMembers, sideMembers, sideByKey) {
|
|
217
|
+
function existingMemberReasons(region, side, baseMembers, sideMembers, sideByKey, allowExistingMemberChanges) {
|
|
216
218
|
const reasonCodes = [];
|
|
217
219
|
const sideBaseOrder = sideMembers.map((member) => member.key).filter((key) => baseMembers.some((baseMember) => baseMember.key === key));
|
|
218
220
|
const baseOrder = baseMembers.map((member) => member.key);
|
|
@@ -225,7 +227,7 @@ function existingMemberReasons(region, side, baseMembers, sideMembers, sideByKey
|
|
|
225
227
|
reasonCodes.push(regionReason(region, `existing-member-removed:${side}:${baseMember.key}`));
|
|
226
228
|
continue;
|
|
227
229
|
}
|
|
228
|
-
if (normalizeMemberText(sideMember.text) !== normalizeMemberText(baseMember.text)) {
|
|
230
|
+
if (!allowExistingMemberChanges && normalizeMemberText(sideMember.text, normalizeKind(region.kind)) !== normalizeMemberText(baseMember.text, normalizeKind(region.kind))) {
|
|
229
231
|
reasonCodes.push(regionReason(region, `existing-member-changed:${side}:${baseMember.key}`));
|
|
230
232
|
}
|
|
231
233
|
}
|
|
@@ -25,12 +25,16 @@ function safeMergeJsTsSource(input = {}) {
|
|
|
25
25
|
return composedBlockedResult(input, 'member-analysis', memberNeutralization.result, memberNeutralization.analysis);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const topLevelInput = {
|
|
29
29
|
...input,
|
|
30
30
|
baseSourceText: memberNeutralization.baseSourceText,
|
|
31
31
|
workerSourceText: memberNeutralization.workerSourceText,
|
|
32
32
|
headSourceText: memberNeutralization.headSourceText
|
|
33
|
-
}
|
|
33
|
+
};
|
|
34
|
+
const topLevelLedgerResult = safeMergeJsTsImportsAndDeclarations(topLevelInput);
|
|
35
|
+
const topLevelResult = topLevelLedgerResult.status === JsTsSafeMergeStatuses.merged
|
|
36
|
+
? topLevelLedgerResult
|
|
37
|
+
: semanticEditFallbackResult(topLevelInput, topLevelLedgerResult);
|
|
34
38
|
if (topLevelResult.status !== JsTsSafeMergeStatuses.merged) {
|
|
35
39
|
return composedBlockedResult(input, 'top-level', topLevelResult, memberNeutralization.analysis);
|
|
36
40
|
}
|
|
@@ -61,7 +65,7 @@ function safeMergeJsTsSource(input = {}) {
|
|
|
61
65
|
metadata: {
|
|
62
66
|
...topLevelResult.metadata,
|
|
63
67
|
composed: {
|
|
64
|
-
phases:
|
|
68
|
+
phases: composedPhaseList(topLevelResult),
|
|
65
69
|
memberRegions: memberNeutralization.analysis.preparedRegions.map((region) => ({
|
|
66
70
|
kind: region.kind,
|
|
67
71
|
name: region.name,
|
|
@@ -78,6 +82,13 @@ function safeMergeJsTsSource(input = {}) {
|
|
|
78
82
|
};
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
function composedPhaseList(topLevelResult) {
|
|
86
|
+
const topLevelPhases = topLevelResult.metadata?.composed?.phases;
|
|
87
|
+
if (Array.isArray(topLevelPhases) && topLevelPhases.length) return [...topLevelPhases, 'member'];
|
|
88
|
+
const topLevelPhase = topLevelResult.metadata?.composed?.phase;
|
|
89
|
+
return topLevelPhase ? [topLevelPhase, 'member'] : ['top-level', 'member'];
|
|
90
|
+
}
|
|
91
|
+
|
|
81
92
|
function hasMemberMergePolicy(input) {
|
|
82
93
|
const policy = input.policy ?? input.mergePolicy ?? input;
|
|
83
94
|
const regions = Array.isArray(policy)
|
|
@@ -119,14 +130,20 @@ function composedBlockedResult(input, phase, result, memberAnalysis) {
|
|
|
119
130
|
gatesPassed: result.gates?.filter((gate) => gate.status === 'passed').length ?? 0,
|
|
120
131
|
memberRegions: memberAnalysis?.preparedRegions?.length ?? 0,
|
|
121
132
|
memberAdditions: 0,
|
|
133
|
+
semanticEditOperations: result.summary?.semanticEditOperations,
|
|
134
|
+
semanticEditAppliedOperations: result.summary?.semanticEditAppliedOperations,
|
|
135
|
+
semanticEditReplayStatus: result.summary?.semanticEditReplayStatus,
|
|
122
136
|
composedPhases: phase === 'top-level' ? 2 : 1
|
|
123
137
|
},
|
|
124
138
|
metadata: {
|
|
125
139
|
composed: {
|
|
126
140
|
phase,
|
|
127
|
-
sourceKind: result.kind
|
|
141
|
+
sourceKind: result.kind,
|
|
142
|
+
topLevelPhase: result.metadata?.composed?.phase,
|
|
143
|
+
topLevelPhases: result.metadata?.composed?.phases
|
|
128
144
|
}
|
|
129
|
-
}
|
|
145
|
+
},
|
|
146
|
+
semanticArtifacts: result.semanticArtifacts
|
|
130
147
|
};
|
|
131
148
|
}
|
|
132
149
|
|
|
@@ -12,6 +12,20 @@ function canonicalizeSourceBodies(sourceText, preparedRegions, side) {
|
|
|
12
12
|
return output;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function removePreparedMemberAdditions(sourceText, preparedRegions, side) {
|
|
16
|
+
let output = sourceText;
|
|
17
|
+
const replacements = preparedRegions
|
|
18
|
+
.map((region) => ({
|
|
19
|
+
range: region[side],
|
|
20
|
+
replacement: removeMembersFromBody(region[side].body, region[`${side}AddedMembers`] ?? [], region.kind)
|
|
21
|
+
}))
|
|
22
|
+
.sort((left, right) => right.range.bodyStart - left.range.bodyStart);
|
|
23
|
+
for (const { range, replacement } of replacements) {
|
|
24
|
+
output = `${output.slice(0, range.bodyStart)}${replacement}${output.slice(range.bodyEnd)}`;
|
|
25
|
+
}
|
|
26
|
+
return output;
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
function applyMemberAdditions(headSourceText, preparedRegions) {
|
|
16
30
|
return applyPreparedMemberAdditions(headSourceText, preparedRegions, ['worker']).sourceText;
|
|
17
31
|
}
|
|
@@ -28,7 +42,7 @@ function applyPreparedMemberAdditions(sourceText, preparedRegions, sides = ['wor
|
|
|
28
42
|
}
|
|
29
43
|
const members = sides.flatMap((side) => region[`${side}AddedMembers`] ?? []);
|
|
30
44
|
if (!members.length) return undefined;
|
|
31
|
-
return { range: match.value, replacement: appendMembersToBody(match.value.body, members) };
|
|
45
|
+
return { range: match.value, replacement: appendMembersToBody(match.value.body, members, region.kind) };
|
|
32
46
|
})
|
|
33
47
|
.filter(Boolean)
|
|
34
48
|
.sort((left, right) => right.range.bodyStart - left.range.bodyStart);
|
|
@@ -38,32 +52,50 @@ function applyPreparedMemberAdditions(sourceText, preparedRegions, sides = ['wor
|
|
|
38
52
|
return { sourceText: output, reasonCodes: uniqueStrings(reasonCodes) };
|
|
39
53
|
}
|
|
40
54
|
|
|
41
|
-
function appendMembersToBody(body, members) {
|
|
55
|
+
function appendMembersToBody(body, members, kind) {
|
|
42
56
|
if (!members.length) return body;
|
|
43
57
|
const indent = inferMemberIndent(body) ?? inferMemberIndent(members.map((member) => member.text).join('\n')) ?? ' ';
|
|
44
|
-
const addedText = members.map((member) => normalizeMemberForInsertion(member.text, indent)).join('\n');
|
|
58
|
+
const addedText = members.map((member, index) => normalizeMemberForInsertion(member.text, indent, kind, index < members.length - 1)).join('\n');
|
|
45
59
|
const trailing = body.match(/\s*$/)?.[0] ?? '';
|
|
46
60
|
const before = body.slice(0, body.length - trailing.length);
|
|
47
61
|
if (!before.trim()) {
|
|
48
62
|
const closingIndent = trailing.includes('\n') ? trailing.slice(trailing.lastIndexOf('\n') + 1) : '';
|
|
49
63
|
return `\n${addedText}\n${closingIndent}`;
|
|
50
64
|
}
|
|
51
|
-
|
|
65
|
+
const appendableBefore = appendReadyBody(before, kind);
|
|
66
|
+
return `${appendableBefore}${appendableBefore.endsWith('\n') ? '' : '\n'}${addedText}${trailing.includes('\n') ? trailing : `\n${trailing}`}`;
|
|
52
67
|
}
|
|
53
68
|
|
|
54
69
|
function inferMemberIndent(text) {
|
|
55
70
|
return String(text ?? '').match(/\n([ \t]*)\S/)?.[1];
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
function normalizeMemberForInsertion(text, indent) {
|
|
73
|
+
function normalizeMemberForInsertion(text, indent, kind, needsDelimiter) {
|
|
59
74
|
const lines = String(text ?? '').replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
60
75
|
while (lines.length && !lines[0].trim()) lines.shift();
|
|
61
76
|
while (lines.length && !lines[lines.length - 1].trim()) lines.pop();
|
|
62
77
|
const commonIndent = minimumIndent(lines);
|
|
63
|
-
|
|
78
|
+
const normalized = lines.map((line) => {
|
|
64
79
|
const normalized = commonIndent ? line.slice(Math.min(commonIndent, leadingWhitespace(line))) : line;
|
|
65
80
|
return normalized.trim() ? `${indent}${normalized}` : normalized;
|
|
66
81
|
}).join('\n');
|
|
82
|
+
if (kind !== 'object' || !needsDelimiter || /,\s*$/.test(normalized)) return normalized;
|
|
83
|
+
return `${normalized},`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function appendReadyBody(before, kind) {
|
|
87
|
+
if (kind !== 'object' || !before.trim() || /,\s*$/.test(before)) return before;
|
|
88
|
+
return `${before.replace(/\s*$/, '')},`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function removeMembersFromBody(body, members, kind) {
|
|
92
|
+
let output = body;
|
|
93
|
+
for (const member of [...members].sort((left, right) => right.start - left.start || right.end - left.end)) {
|
|
94
|
+
output = `${output.slice(0, member.start)}${output.slice(member.end)}`;
|
|
95
|
+
}
|
|
96
|
+
if (kind !== 'object') return output;
|
|
97
|
+
const trailing = String(body ?? '').match(/\s*$/)?.[0] ?? '';
|
|
98
|
+
return output.replace(/,\s*$/, trailing);
|
|
67
99
|
}
|
|
68
100
|
|
|
69
101
|
function minimumIndent(lines) {
|
|
@@ -78,5 +110,6 @@ function leadingWhitespace(line) {
|
|
|
78
110
|
export {
|
|
79
111
|
applyMemberAdditions,
|
|
80
112
|
applyPreparedMemberAdditions,
|
|
81
|
-
canonicalizeSourceBodies
|
|
113
|
+
canonicalizeSourceBodies,
|
|
114
|
+
removePreparedMemberAdditions
|
|
82
115
|
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
function normalizeMemberText(text) {
|
|
2
|
-
|
|
1
|
+
function normalizeMemberText(text, kind) {
|
|
2
|
+
const normalized = String(text ?? '')
|
|
3
3
|
.replace(/\r\n/g, '\n')
|
|
4
4
|
.replace(/\r/g, '\n')
|
|
5
5
|
.split('\n')
|
|
6
6
|
.map((line) => line.trimEnd())
|
|
7
7
|
.join('\n')
|
|
8
8
|
.trim();
|
|
9
|
+
if (kind === 'object') return normalized.replace(/,\s*$/, '');
|
|
10
|
+
if (kind === 'interface' || kind === 'type' || kind === 'class') return normalized.replace(/;\s*$/, '');
|
|
11
|
+
return normalized;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
function uniqueStrings(values) {
|
package/package.json
CHANGED