@shapeshift-labs/frontier-lang-compiler 0.2.104 → 0.2.106

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.
@@ -0,0 +1,197 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { idFragment } from './native-import-utils.js';
3
+ import { detectLineEnding, normalizeLineEndings } from './js-ts-safe-merge-context.js';
4
+
5
+ function createOperationsFromLedgers(context) {
6
+ const headByKey = new Map(context.head.entries.map((entry) => [entry.key, entry]));
7
+ return context.merged.entries.flatMap((entry, index) => {
8
+ const headEntry = headByKey.get(entry.key);
9
+ if (headEntry) {
10
+ if (sameEntryText(headEntry.text, entry.text)) return [];
11
+ return [operationRecord({
12
+ ...context,
13
+ index,
14
+ kind: entry.kind === 'import' ? 'jsTsReplaceImport' : 'jsTsReplaceDeclaration',
15
+ changeKind: 'modified',
16
+ entry,
17
+ headEntry,
18
+ insertion: undefined,
19
+ currentText: headEntry.text,
20
+ replacementText: entry.text
21
+ })];
22
+ }
23
+ return [operationRecord({
24
+ ...context,
25
+ index,
26
+ kind: entry.kind === 'import' ? 'jsTsInsertImport' : 'jsTsInsertDeclaration',
27
+ changeKind: 'added',
28
+ entry,
29
+ headEntry: undefined,
30
+ insertion: insertionAnchorForMergedEntry(entry, index, context),
31
+ currentText: '',
32
+ replacementText: insertionText(entry.text, context.head.sourceText)
33
+ })];
34
+ });
35
+ }
36
+
37
+ function operationRecord(input) {
38
+ const anchor = entryAnchor(input.entry, input.sourcePath, input.language);
39
+ const operation = {
40
+ id: `js_ts_safe_merge_op_${idFragment([input.id, input.index, input.entry.key].join(':'))}`,
41
+ kind: input.kind,
42
+ changeKind: input.changeKind,
43
+ anchor,
44
+ insertion: input.insertion,
45
+ spans: {
46
+ head: input.headEntry ? spanForEntry(input.headEntry) : undefined,
47
+ worker: spanForEntry(input.entry)
48
+ },
49
+ hashes: {
50
+ baseSourceHash: input.input.baseHash,
51
+ workerSourceHash: input.input.workerHash,
52
+ headSourceHash: input.input.headHash,
53
+ headTextHash: hashSemanticValue(input.currentText),
54
+ workerTextHash: hashSemanticValue(input.replacementText)
55
+ },
56
+ status: 'portable',
57
+ readiness: 'ready',
58
+ confidence: 1,
59
+ reasonCodes: ['js-ts-safe-merge-gates-passed', 'js-ts-ledger-source-edit'],
60
+ evidenceIds: [`evidence_${idFragment(input.id)}_js_ts_safe_merge_semantic_replay`],
61
+ metadata: {
62
+ autoMergeClaim: false,
63
+ semanticEquivalenceClaim: false,
64
+ ledgerKey: input.entry.key
65
+ }
66
+ };
67
+ return { ...operation, operationContentHash: hashSemanticValue(operation) };
68
+ }
69
+
70
+ function sourceEditForOperation(operation, order, headSourceText, mergedSourceText) {
71
+ const headStart = operation.spans.head?.start ?? insertionOffsetFromAnchor(operation.insertion, headSourceText);
72
+ const headEnd = operation.spans.head?.end ?? headStart;
73
+ const workerStart = operation.spans.worker?.start ?? 0;
74
+ const workerEnd = operation.spans.worker?.end ?? workerStart;
75
+ const current = headSourceText.slice(headStart, headEnd);
76
+ const replacementSpanText = mergedSourceText.slice(workerStart, workerEnd);
77
+ const replacement = operation.changeKind === 'added'
78
+ ? insertionText(replacementSpanText, headSourceText)
79
+ : replacementSpanText;
80
+ return {
81
+ operationId: operation.id,
82
+ order,
83
+ kind: operation.kind,
84
+ editKind: operation.changeKind === 'added' ? 'insert' : 'replace',
85
+ changeKind: operation.changeKind,
86
+ anchorKey: operation.anchor.key,
87
+ conflictKey: operation.anchor.conflictKey,
88
+ regionId: operation.anchor.regionId,
89
+ regionKind: operation.anchor.regionKind,
90
+ sourcePath: operation.anchor.sourcePath,
91
+ symbolId: operation.anchor.symbolId,
92
+ symbolName: operation.anchor.symbolName,
93
+ symbolKind: operation.anchor.symbolKind,
94
+ operationContentHash: operation.operationContentHash,
95
+ insertion: operation.insertion,
96
+ start: headStart,
97
+ end: headEnd,
98
+ workerStart,
99
+ workerEnd,
100
+ current,
101
+ replacement,
102
+ replacementSpanText
103
+ };
104
+ }
105
+
106
+ function insertionAnchorForMergedEntry(entry, index, context) {
107
+ const previous = nearestHeadEntry(context.merged.entries, context.head.entries, index, -1);
108
+ if (previous) return {
109
+ mode: 'after',
110
+ anchorKey: previous.key,
111
+ anchorSymbolName: entryName(previous),
112
+ anchorSymbolKind: entrySymbolKind(previous),
113
+ headSpan: spanForEntry(previous),
114
+ sourcePath: context.sourcePath
115
+ };
116
+ const next = nearestHeadEntry(context.merged.entries, context.head.entries, index, 1);
117
+ if (next) return {
118
+ mode: 'before',
119
+ anchorKey: next.key,
120
+ anchorSymbolName: entryName(next),
121
+ anchorSymbolKind: entrySymbolKind(next),
122
+ headSpan: spanForEntry(next),
123
+ sourcePath: context.sourcePath
124
+ };
125
+ return { mode: 'file-end', sourcePath: context.sourcePath };
126
+ }
127
+
128
+ function nearestHeadEntry(mergedEntries, headEntries, startIndex, step) {
129
+ const headByKey = new Map(headEntries.map((entry) => [entry.key, entry]));
130
+ for (let index = startIndex + step; index >= 0 && index < mergedEntries.length; index += step) {
131
+ const headEntry = headByKey.get(mergedEntries[index].key);
132
+ if (headEntry) return headEntry;
133
+ }
134
+ return undefined;
135
+ }
136
+
137
+ function insertionOffsetFromAnchor(insertion, sourceText) {
138
+ if (insertion?.mode === 'file-start') return 0;
139
+ if (insertion?.mode === 'file-end') return sourceText.length;
140
+ const span = insertion?.headSpan;
141
+ if (!span) return sourceText.length;
142
+ if (insertion.mode === 'before') return lineStartOffset(sourceText, span.start);
143
+ return lineEndOffset(sourceText, span.end);
144
+ }
145
+
146
+ function entryAnchor(entry, sourcePath, language) {
147
+ const name = entryName(entry);
148
+ const key = `source#${sourcePath ?? 'unknown'}#${entry.kind}#${name ?? entry.key}`;
149
+ return {
150
+ key,
151
+ conflictKey: key,
152
+ regionId: key,
153
+ regionKind: entry.kind === 'import' ? 'import' : 'declaration',
154
+ granularity: 'js-ts-ledger-entry',
155
+ language,
156
+ sourcePath,
157
+ symbolId: key,
158
+ symbolName: name,
159
+ symbolKind: entrySymbolKind(entry),
160
+ sourceSpan: spanForEntry(entry)
161
+ };
162
+ }
163
+
164
+ function entryName(entry) {
165
+ if (entry.kind === 'import') return entry.importInfo?.moduleSpecifier;
166
+ return entry.names?.join('|') ?? entry.key;
167
+ }
168
+
169
+ function entrySymbolKind(entry) {
170
+ if (entry.kind === 'import') return 'import';
171
+ return entry.declarationInfo?.declarationKind ?? entry.kind;
172
+ }
173
+
174
+ function spanForEntry(entry) {
175
+ return { start: entry.start, end: entry.end };
176
+ }
177
+
178
+ function insertionText(text, sourceText) {
179
+ const lineEnding = detectLineEnding(sourceText);
180
+ const normalized = normalizeLineEndings(String(text ?? '').trimEnd(), lineEnding);
181
+ return normalized.endsWith(lineEnding) ? normalized : `${normalized}${lineEnding}`;
182
+ }
183
+
184
+ function sameEntryText(left, right) {
185
+ return normalizeLineEndings(String(left ?? '').trim(), '\n') === normalizeLineEndings(String(right ?? '').trim(), '\n');
186
+ }
187
+
188
+ function lineStartOffset(sourceText, offset) {
189
+ return sourceText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1;
190
+ }
191
+
192
+ function lineEndOffset(sourceText, offset) {
193
+ const lineEnd = sourceText.indexOf('\n', offset);
194
+ return lineEnd === -1 ? sourceText.length : lineEnd + 1;
195
+ }
196
+
197
+ export { createOperationsFromLedgers, sourceEditForOperation };
@@ -0,0 +1,266 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { idFragment, uniqueStrings } from './native-import-utils.js';
3
+ import { replaySemanticEditProjection } from './internal/index-impl/replaySemanticEditProjection.js';
4
+ import { projectionEditRecord } from './internal/index-impl/semanticEditProjectionRecord.js';
5
+ import { applySourceEdits, dedupeSourceEdits, validateSourceEdits } from './internal/index-impl/semanticSourceEditDedupe.js';
6
+ import { createMergeContext } from './js-ts-safe-merge-context.js';
7
+ import { scanJsTsTopLevelLedger } from './js-ts-safe-merge-ledger.js';
8
+ import { createOperationsFromLedgers, sourceEditForOperation } from './js-ts-safe-merge-semantic-artifact-ledger.js';
9
+
10
+ function createJsTsSafeMergeSemanticArtifacts(input = {}, merge = {}) {
11
+ const headSourceText = input.headSourceText;
12
+ const mergedSourceText = merge.mergedSourceText ?? merge.outputSourceText;
13
+ const language = input.language ?? merge.language ?? 'typescript';
14
+ const sourcePath = input.sourcePath ?? merge.sourcePath ?? 'inline.js';
15
+ const id = String(input.id ?? merge.id ?? 'js_ts_safe_merge');
16
+ const baseReasonCodes = [];
17
+ if (typeof headSourceText !== 'string') baseReasonCodes.push('missing-head-source-text');
18
+ if (typeof mergedSourceText !== 'string') baseReasonCodes.push('missing-merged-source-text');
19
+ if (baseReasonCodes.length) return blockedArtifacts({ id, language, sourcePath, reasonCodes: baseReasonCodes, merge });
20
+
21
+ const ledgerContext = createMergeContext({ ...input, id: `${id}_semantic_artifacts`, language, sourcePath });
22
+ const head = scanJsTsTopLevelLedger(headSourceText, 'head', ledgerContext);
23
+ const merged = scanJsTsTopLevelLedger(mergedSourceText, 'merged', ledgerContext);
24
+ if (ledgerContext.conflicts.length) {
25
+ return blockedArtifacts({
26
+ id,
27
+ language,
28
+ sourcePath,
29
+ reasonCodes: uniqueStrings(ledgerContext.conflicts.map((conflict) => conflict.code)),
30
+ merge,
31
+ ledgers: { head, merged }
32
+ });
33
+ }
34
+
35
+ const operations = createOperationsFromLedgers({ id, language, sourcePath, head, merged, input, merge });
36
+ const rawEdits = operations.map((operation, order) => sourceEditForOperation(operation, order, headSourceText, mergedSourceText));
37
+ const deduped = dedupeSourceEdits(rawEdits);
38
+ const reasonCodes = uniqueStrings([
39
+ ...validateSourceEdits(deduped.edits),
40
+ ...deduped.skippedOperationIds.map((operationId) => `source-edit-deduped:${operationId}`)
41
+ ]);
42
+ const sourceText = reasonCodes.length ? undefined : applySourceEdits(headSourceText, deduped.edits);
43
+ if (sourceText !== mergedSourceText) reasonCodes.push('projected-source-mismatch');
44
+ const blocked = reasonCodes.length > 0;
45
+ const script = createScript({ id, language, sourcePath, input, merge, operations, blocked, reasonCodes });
46
+ const projection = createProjection({
47
+ id,
48
+ language,
49
+ sourcePath,
50
+ input,
51
+ merge,
52
+ script,
53
+ edits: blocked ? [] : deduped.edits,
54
+ skippedOperationIds: deduped.skippedOperationIds,
55
+ sourceText: blocked ? undefined : sourceText,
56
+ reasonCodes
57
+ });
58
+ const replay = replaySemanticEditProjection({
59
+ id: `js_ts_safe_merge_replay_${idFragment(id)}`,
60
+ projection,
61
+ currentSourceText: headSourceText,
62
+ currentSourcePath: sourcePath,
63
+ language
64
+ });
65
+ const alreadyAppliedReplay = replaySemanticEditProjection({
66
+ id: `js_ts_safe_merge_replay_already_applied_${idFragment(id)}`,
67
+ projection,
68
+ currentSourceText: mergedSourceText,
69
+ currentSourcePath: sourcePath,
70
+ language
71
+ });
72
+ const replayReady = replay.status === 'accepted-clean' && replay.outputSourceText === mergedSourceText;
73
+ const alreadyAppliedReady = alreadyAppliedReplay.status === 'already-applied';
74
+ const status = !blocked && replayReady && alreadyAppliedReady ? 'verified' : 'blocked';
75
+ const finalReasonCodes = uniqueStrings([
76
+ ...reasonCodes,
77
+ replayReady ? undefined : `semantic-replay-${replay.status}`,
78
+ alreadyAppliedReady ? undefined : `semantic-replay-already-applied-${alreadyAppliedReplay.status}`
79
+ ]);
80
+ const core = {
81
+ kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
82
+ version: 1,
83
+ schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
84
+ id: `js_ts_safe_merge_semantic_artifacts_${idFragment(id)}`,
85
+ sourcePath,
86
+ language,
87
+ status,
88
+ script,
89
+ projection,
90
+ replay,
91
+ alreadyAppliedReplay,
92
+ admission: {
93
+ status: status === 'verified' ? 'auto-merge-candidate' : 'blocked',
94
+ action: status === 'verified' ? 'apply' : 'human-review',
95
+ reviewRequired: status !== 'verified',
96
+ autoApplyCandidate: status === 'verified',
97
+ autoMergeClaim: false,
98
+ semanticEquivalenceClaim: false,
99
+ reasonCodes: finalReasonCodes
100
+ },
101
+ summary: {
102
+ operations: operations.length,
103
+ edits: projection.edits.length,
104
+ replayStatus: replay.status,
105
+ alreadyAppliedReplayStatus: alreadyAppliedReplay.status,
106
+ projectedSourceMatchesMerged: projection.sourceText === mergedSourceText,
107
+ replayOutputMatchesMerged: replay.outputSourceText === mergedSourceText
108
+ },
109
+ evidence: [{
110
+ id: `evidence_${idFragment(id)}_js_ts_safe_merge_semantic_replay`,
111
+ kind: 'js-ts-safe-merge-semantic-replay',
112
+ status: status === 'verified' ? 'passed' : 'needs-review',
113
+ path: sourcePath,
114
+ summary: status === 'verified'
115
+ ? `JS/TS safe merge replay verified ${operations.length} semantic source edit(s).`
116
+ : `JS/TS safe merge semantic replay requires review: ${finalReasonCodes.join(', ')}.`,
117
+ metadata: {
118
+ scriptId: script.id,
119
+ projectionId: projection.id,
120
+ replayId: replay.id,
121
+ alreadyAppliedReplayId: alreadyAppliedReplay.id,
122
+ autoMergeClaim: false,
123
+ semanticEquivalenceClaim: false
124
+ }
125
+ }],
126
+ metadata: {
127
+ autoMergeClaim: false,
128
+ semanticEquivalenceClaim: false,
129
+ source: 'js-ts-safe-merge-ledger',
130
+ mergeGateIds: (merge.gates ?? []).map((gate) => gate.id)
131
+ }
132
+ };
133
+ return { ...core, hash: hashSemanticValue(core) };
134
+ }
135
+
136
+ function createScript(input) {
137
+ const statuses = countBy(input.operations, (operation) => operation.status);
138
+ const kinds = countBy(input.operations, (operation) => operation.kind);
139
+ const reasonCodes = uniqueStrings(input.reasonCodes);
140
+ const core = {
141
+ kind: 'frontier.lang.semanticEditScript',
142
+ version: 1,
143
+ schema: 'frontier.lang.semanticEditScript.v1',
144
+ id: `js_ts_safe_merge_script_${idFragment(input.id)}`,
145
+ stableId: `js_ts_safe_merge_script_${idFragment([input.id, input.merge.mergedSourceText].join(':'))}`,
146
+ language: input.language,
147
+ sourcePath: input.sourcePath,
148
+ baseHash: input.input.baseHash,
149
+ workerHash: input.input.workerHash,
150
+ headHash: input.input.headHash,
151
+ workerChangeSetId: input.input.workerChangeSetId,
152
+ headChangeSetId: input.input.headChangeSetId,
153
+ operations: input.operations,
154
+ summary: {
155
+ operations: input.operations.length,
156
+ byStatus: statuses,
157
+ byKind: kinds,
158
+ portable: statuses.portable ?? 0,
159
+ alreadyApplied: 0,
160
+ needsPort: 0,
161
+ conflicts: 0,
162
+ stale: 0,
163
+ blocked: input.blocked ? input.operations.length : 0,
164
+ candidates: 0,
165
+ autoMergeCandidates: input.blocked ? 0 : input.operations.length,
166
+ operationContentHashes: input.operations.map((operation) => operation.operationContentHash)
167
+ },
168
+ admission: {
169
+ status: input.blocked ? 'blocked' : 'auto-merge-candidate',
170
+ action: input.blocked ? 'block' : 'run-gates-and-apply',
171
+ reviewRequired: input.blocked,
172
+ autoApplyCandidate: !input.blocked,
173
+ autoMergeClaim: false,
174
+ semanticEquivalenceClaim: false,
175
+ reasonCodes,
176
+ conflictKeys: input.operations.map((operation) => operation.anchor.conflictKey),
177
+ evidenceIds: input.operations.flatMap((operation) => operation.evidenceIds ?? [])
178
+ },
179
+ evidence: [],
180
+ metadata: {
181
+ autoMergeClaim: false,
182
+ semanticEquivalenceClaim: false,
183
+ source: 'js-ts-safe-merge-ledger'
184
+ }
185
+ };
186
+ return { ...core, hash: hashSemanticValue(core) };
187
+ }
188
+
189
+ function createProjection(input) {
190
+ const blocked = input.reasonCodes.length > 0;
191
+ const edits = blocked ? [] : input.edits.map(projectionEditRecord);
192
+ const core = {
193
+ kind: 'frontier.lang.semanticEditProjection',
194
+ version: 1,
195
+ id: `js_ts_safe_merge_projection_${idFragment(input.id)}`,
196
+ scriptId: input.script.id,
197
+ status: blocked ? 'blocked' : 'projected',
198
+ sourcePath: input.sourcePath,
199
+ language: input.language,
200
+ baseHash: input.input.baseHash,
201
+ workerHash: input.input.workerHash,
202
+ headHash: input.input.headHash,
203
+ projectedHash: input.sourceText === undefined ? undefined : hashSemanticValue(input.sourceText),
204
+ appliedOperations: edits.map((edit) => edit.operationId).filter(Boolean),
205
+ skippedOperations: blocked ? input.script.operations.map((operation) => operation.id) : input.skippedOperationIds,
206
+ edits,
207
+ sourceText: input.sourceText,
208
+ admission: {
209
+ status: blocked ? 'blocked' : 'auto-merge-candidate',
210
+ autoMergeClaim: false,
211
+ semanticEquivalenceClaim: false,
212
+ reasonCodes: uniqueStrings(input.reasonCodes)
213
+ },
214
+ metadata: {
215
+ autoMergeClaim: false,
216
+ semanticEquivalenceClaim: false,
217
+ source: 'js-ts-safe-merge-ledger'
218
+ }
219
+ };
220
+ return { ...core, hash: hashSemanticValue(core) };
221
+ }
222
+
223
+ function blockedArtifacts(input) {
224
+ const core = {
225
+ kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
226
+ version: 1,
227
+ schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
228
+ id: `js_ts_safe_merge_semantic_artifacts_${idFragment(input.id)}`,
229
+ sourcePath: input.sourcePath,
230
+ language: input.language,
231
+ status: 'blocked',
232
+ admission: {
233
+ status: 'blocked',
234
+ action: 'human-review',
235
+ reviewRequired: true,
236
+ autoApplyCandidate: false,
237
+ autoMergeClaim: false,
238
+ semanticEquivalenceClaim: false,
239
+ reasonCodes: uniqueStrings(input.reasonCodes)
240
+ },
241
+ summary: {
242
+ operations: 0,
243
+ edits: 0,
244
+ projectedSourceMatchesMerged: false,
245
+ replayOutputMatchesMerged: false
246
+ },
247
+ evidence: [],
248
+ metadata: {
249
+ autoMergeClaim: false,
250
+ semanticEquivalenceClaim: false,
251
+ source: 'js-ts-safe-merge-ledger'
252
+ }
253
+ };
254
+ return { ...core, hash: hashSemanticValue(core) };
255
+ }
256
+
257
+ function countBy(values, keyFor) {
258
+ const result = {};
259
+ for (const value of values) {
260
+ const key = keyFor(value);
261
+ result[key] = (result[key] ?? 0) + 1;
262
+ }
263
+ return result;
264
+ }
265
+
266
+ export { createJsTsSafeMergeSemanticArtifacts };
@@ -8,6 +8,7 @@ import {
8
8
  } from './js-ts-safe-merge-constants.js';
9
9
  import { indexBaseLedger, scanJsTsTopLevelLedger, validateLedgerUniqueness } from './js-ts-safe-merge-ledger.js';
10
10
  import { applySourceMergePlan, createSourceMergePlan } from './js-ts-safe-merge-plan.js';
11
+ import { createJsTsSafeMergeSemanticArtifacts } from './js-ts-safe-merge-semantic-artifacts.js';
11
12
 
12
13
  export { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds, JsTsSafeMergeStatuses };
13
14
 
@@ -61,7 +62,7 @@ export function safeMergeJsTsImportsAndDeclarations(input = {}) {
61
62
  if (!context.conflicts.length) validateLedgerUniqueness(merged, context);
62
63
  if (context.conflicts.length) return blockedResult(context, { base, worker, head, merged });
63
64
 
64
- return {
65
+ const result = {
65
66
  kind: 'frontier.lang.jsTsSafeMerge',
66
67
  version: 1,
67
68
  schema: 'frontier.lang.jsTsSafeMerge.v1',
@@ -99,6 +100,10 @@ export function safeMergeJsTsImportsAndDeclarations(input = {}) {
99
100
  currentSourceHash: input.currentSourceHash
100
101
  })
101
102
  };
103
+ return {
104
+ ...result,
105
+ semanticArtifacts: createJsTsSafeMergeSemanticArtifacts(input, result)
106
+ };
102
107
  }
103
108
 
104
109
  function validateStaleSourceHashes(input, context) {
@@ -1,3 +1,6 @@
1
+ import { findContainer } from './js-ts-semantic-merge-member-containers.js';
2
+ import { uniqueStrings } from './js-ts-semantic-merge-member-utils.js';
3
+
1
4
  function canonicalizeSourceBodies(sourceText, preparedRegions, side) {
2
5
  let output = sourceText;
3
6
  const replacements = preparedRegions
@@ -10,15 +13,29 @@ function canonicalizeSourceBodies(sourceText, preparedRegions, side) {
10
13
  }
11
14
 
12
15
  function applyMemberAdditions(headSourceText, preparedRegions) {
13
- let output = headSourceText;
16
+ return applyPreparedMemberAdditions(headSourceText, preparedRegions, ['worker']).sourceText;
17
+ }
18
+
19
+ function applyPreparedMemberAdditions(sourceText, preparedRegions, sides = ['worker']) {
20
+ let output = sourceText;
21
+ const reasonCodes = [];
14
22
  const replacements = preparedRegions
15
- .filter((region) => region.workerAddedMembers.length)
16
- .map((region) => ({ range: region.head, replacement: appendMembersToBody(region.head.body, region.workerAddedMembers) }))
23
+ .map((region) => {
24
+ const match = findContainer(sourceText, region.policy);
25
+ if (match.reasonCodes.length) {
26
+ reasonCodes.push(...match.reasonCodes.map((reason) => `target-${reason}:${region.kind}:${region.name}`));
27
+ return undefined;
28
+ }
29
+ const members = sides.flatMap((side) => region[`${side}AddedMembers`] ?? []);
30
+ if (!members.length) return undefined;
31
+ return { range: match.value, replacement: appendMembersToBody(match.value.body, members) };
32
+ })
33
+ .filter(Boolean)
17
34
  .sort((left, right) => right.range.bodyStart - left.range.bodyStart);
18
35
  for (const { range, replacement } of replacements) {
19
36
  output = `${output.slice(0, range.bodyStart)}${replacement}${output.slice(range.bodyEnd)}`;
20
37
  }
21
- return output;
38
+ return { sourceText: output, reasonCodes: uniqueStrings(reasonCodes) };
22
39
  }
23
40
 
24
41
  function appendMembersToBody(body, members) {
@@ -60,5 +77,6 @@ function leadingWhitespace(line) {
60
77
 
61
78
  export {
62
79
  applyMemberAdditions,
80
+ applyPreparedMemberAdditions,
63
81
  canonicalizeSourceBodies
64
82
  };
@@ -7,6 +7,7 @@ export {
7
7
  } from './js-ts-semantic-merge-member-segments.js';
8
8
  export {
9
9
  applyMemberAdditions,
10
+ applyPreparedMemberAdditions,
10
11
  canonicalizeSourceBodies
11
12
  } from './js-ts-semantic-merge-member-source.js';
12
13
  export {
@@ -4,6 +4,9 @@ export {
4
4
  JsTsSafeMergeStatuses,
5
5
  safeMergeJsTsImportsAndDeclarations
6
6
  } from './js-ts-safe-merge.js';
7
+ export {
8
+ safeMergeJsTsSource
9
+ } from './js-ts-safe-merge-composed.js';
7
10
  export {
8
11
  mergeJsTsSafeMemberAdditions,
9
12
  safeMergeJsTsMembers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.104",
3
+ "version": "0.2.106",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",