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

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
@@ -168,7 +168,8 @@ The JS/TS semantic merge smoke corpus lives at
168
168
  `test/smoke/js-ts-semantic-merge-oracles.mjs`. The fixtures are deliberately
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
- merges, safe unordered member merges, and rejected unsafe cases such as stale
171
+ merges, safe unordered member merges, composed top-level/member safe merges,
172
+ and rejected unsafe cases such as stale
172
173
  ledger spans, import specifier reordering, computed keys, duplicate exported
173
174
  names, duplicate object members, decorators, overload anchors, and same-anchor
174
175
  edit conflicts. Fixture failures include the fixture id and the actual
@@ -1,4 +1,4 @@
1
- export type JsTsSafeMemberMergeRegionKind = 'interface' | 'type' | 'object';
1
+ export type JsTsSafeMemberMergeRegionKind = 'interface' | 'type' | 'class' | 'object';
2
2
  export type JsTsSafeMemberMergeOrder = 'non-semantic' | string;
3
3
  export type JsTsSafeMemberMergeStatus = 'merged' | 'rejected';
4
4
 
@@ -26,6 +26,30 @@ export interface JsTsSafeMemberMergeInput {
26
26
  readonly policy?: JsTsSafeMemberMergePolicy | readonly JsTsSafeMemberMergePolicyRegion[];
27
27
  readonly mergePolicy?: JsTsSafeMemberMergePolicy | readonly JsTsSafeMemberMergePolicyRegion[];
28
28
  readonly unorderedRegions?: readonly JsTsSafeMemberMergePolicyRegion[];
29
+ readonly allowNonPolicySourceChanges?: boolean;
30
+ }
31
+
32
+ export interface JsTsSafeMemberMergeConflict {
33
+ readonly code: string;
34
+ readonly gateId: string;
35
+ readonly message: string;
36
+ readonly side?: 'base' | 'worker' | 'head' | string;
37
+ readonly sourcePath?: string;
38
+ readonly details?: Record<string, unknown>;
39
+ }
40
+
41
+ export interface JsTsSafeMemberMergeGate {
42
+ readonly id: string;
43
+ readonly status: 'passed' | 'blocked' | 'skipped' | string;
44
+ readonly reasonCodes: readonly string[];
45
+ }
46
+
47
+ export interface JsTsSafeMemberMergeAdmission {
48
+ readonly status: 'auto-merge-candidate' | 'blocked' | string;
49
+ readonly action: 'apply' | 'human-review' | string;
50
+ readonly reviewRequired: boolean;
51
+ readonly autoApplyCandidate: boolean;
52
+ readonly reasonCodes: readonly string[];
29
53
  }
30
54
 
31
55
  export interface JsTsSafeMemberMergedRegion {
@@ -42,12 +66,16 @@ export interface JsTsSafeMemberMergeResult {
42
66
  readonly status: JsTsSafeMemberMergeStatus;
43
67
  readonly sourceText?: string;
44
68
  readonly reasonCodes: readonly string[];
69
+ readonly conflicts: readonly JsTsSafeMemberMergeConflict[];
70
+ readonly gates: readonly JsTsSafeMemberMergeGate[];
71
+ readonly admission: JsTsSafeMemberMergeAdmission;
45
72
  readonly mergedRegions: readonly JsTsSafeMemberMergedRegion[];
46
73
  readonly summary: {
47
74
  readonly regions: number;
48
75
  readonly workerAdditions: number;
49
76
  readonly headAdditions: number;
50
77
  readonly appliedAdditions: number;
78
+ readonly conflicts: number;
51
79
  };
52
80
  readonly metadata: {
53
81
  readonly explicitPolicy: boolean;
@@ -1,4 +1,8 @@
1
1
  import type { FrontierSourceLanguage } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import type {
3
+ JsTsSafeMemberMergePolicy,
4
+ JsTsSafeMemberMergePolicyRegion
5
+ } from './js-ts-safe-member-merge.js';
2
6
 
3
7
  export type JsTsSafeMergeStatus = 'merged' | 'blocked';
4
8
  export type JsTsSafeMergeGateStatus = 'passed' | 'blocked' | 'skipped';
@@ -65,6 +69,9 @@ export interface JsTsSafeMergeInput {
65
69
  readonly baseSourceLedger?: unknown;
66
70
  readonly workerSourceLedger?: unknown;
67
71
  readonly headSourceLedger?: unknown;
72
+ readonly policy?: JsTsSafeMemberMergePolicy | readonly JsTsSafeMemberMergePolicyRegion[];
73
+ readonly mergePolicy?: JsTsSafeMemberMergePolicy | readonly JsTsSafeMemberMergePolicyRegion[];
74
+ readonly unorderedRegions?: readonly JsTsSafeMemberMergePolicyRegion[];
68
75
  }
69
76
 
70
77
  export interface JsTsSafeMergeConflict {
@@ -98,6 +105,9 @@ export interface JsTsSafeMergeSummary {
98
105
  readonly changedExistingDeclarations: number;
99
106
  readonly conflicts: number;
100
107
  readonly gatesPassed: number;
108
+ readonly memberRegions?: number;
109
+ readonly memberAdditions?: number;
110
+ readonly composedPhases?: number;
101
111
  }
102
112
 
103
113
  export interface JsTsSafeMergeResult {
@@ -118,3 +128,4 @@ export interface JsTsSafeMergeResult {
118
128
  }
119
129
 
120
130
  export declare function safeMergeJsTsImportsAndDeclarations(input: JsTsSafeMergeInput): JsTsSafeMergeResult;
131
+ export declare function safeMergeJsTsSource(input: JsTsSafeMergeInput): JsTsSafeMergeResult;
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  applyMemberAdditions,
3
+ applyPreparedMemberAdditions,
3
4
  canonicalizeSourceBodies,
4
5
  findContainer,
5
6
  normalizeKind,
@@ -17,6 +18,54 @@ function safeMergeJsTsMembers(input = {}) {
17
18
  }
18
19
 
19
20
  function mergeJsTsSafeMemberAdditions(input = {}) {
21
+ const analysis = analyzeJsTsSafeMemberAdditions(input);
22
+ const uniqueReasons = uniqueStrings(analysis.reasonCodes);
23
+ if (uniqueReasons.length) {
24
+ return mergeResult('rejected', undefined, uniqueReasons, analysis.preparedRegions, input, analysis.explicitPolicy);
25
+ }
26
+ const sourceText = applyMemberAdditions(analysis.headSourceText, analysis.preparedRegions);
27
+ return mergeResult('merged', sourceText, [], analysis.preparedRegions, input, analysis.explicitPolicy);
28
+ }
29
+
30
+ function analyzeJsTsSafeMemberAdditions(input = {}) {
31
+ const analysis = prepareJsTsSafeMemberAdditions(input);
32
+ const reasonCodes = [...analysis.reasonCodes];
33
+ if (!input.allowNonPolicySourceChanges) {
34
+ reasonCodes.push(...nonPolicySourceChangeReasons(analysis));
35
+ }
36
+ return {
37
+ ...analysis,
38
+ reasonCodes: uniqueStrings(reasonCodes),
39
+ ok: reasonCodes.length === 0
40
+ };
41
+ }
42
+
43
+ function neutralizeJsTsSafeMemberMergeSources(input = {}) {
44
+ const analysis = analyzeJsTsSafeMemberAdditions({
45
+ ...input,
46
+ allowNonPolicySourceChanges: true
47
+ });
48
+ if (analysis.reasonCodes.length) {
49
+ return {
50
+ ok: false,
51
+ analysis,
52
+ result: mergeResult('rejected', undefined, analysis.reasonCodes, analysis.preparedRegions, input, analysis.explicitPolicy)
53
+ };
54
+ }
55
+ return {
56
+ ok: true,
57
+ analysis,
58
+ baseSourceText: analysis.baseSourceText,
59
+ workerSourceText: canonicalizeSourceBodies(analysis.workerSourceText, analysis.preparedRegions, 'worker'),
60
+ headSourceText: canonicalizeSourceBodies(analysis.headSourceText, analysis.preparedRegions, 'head')
61
+ };
62
+ }
63
+
64
+ function applyJsTsPreparedMemberAdditions(sourceText, preparedRegions, sides) {
65
+ return applyPreparedMemberAdditions(sourceText, preparedRegions, sides);
66
+ }
67
+
68
+ function prepareJsTsSafeMemberAdditions(input = {}) {
20
69
  const baseSourceText = input.baseSourceText;
21
70
  const workerSourceText = input.workerSourceText;
22
71
  const headSourceText = input.headSourceText;
@@ -36,6 +85,20 @@ function mergeJsTsSafeMemberAdditions(input = {}) {
36
85
  reasonCodes.push(...prepared.reasonCodes);
37
86
  if (prepared.ok) preparedRegions.push(prepared.value);
38
87
  }
88
+ const explicitPolicy = policyRegions.length > 0;
89
+ return {
90
+ baseSourceText,
91
+ workerSourceText,
92
+ headSourceText,
93
+ reasonCodes: uniqueStrings(reasonCodes),
94
+ preparedRegions,
95
+ explicitPolicy
96
+ };
97
+ }
98
+
99
+ function nonPolicySourceChangeReasons(analysis) {
100
+ const reasonCodes = [];
101
+ const { baseSourceText, workerSourceText, headSourceText, preparedRegions } = analysis;
39
102
  if (typeof baseSourceText === 'string' && typeof workerSourceText === 'string' && preparedRegions.length) {
40
103
  const canonicalWorker = canonicalizeSourceBodies(workerSourceText, preparedRegions, 'worker');
41
104
  if (canonicalWorker !== baseSourceText) reasonCodes.push('non-policy-source-change:worker');
@@ -44,11 +107,7 @@ function mergeJsTsSafeMemberAdditions(input = {}) {
44
107
  const canonicalHead = canonicalizeSourceBodies(headSourceText, preparedRegions, 'head');
45
108
  if (canonicalHead !== baseSourceText) reasonCodes.push('non-policy-source-change:head');
46
109
  }
47
- const uniqueReasons = uniqueStrings(reasonCodes);
48
- const explicitPolicy = policyRegions.length > 0;
49
- if (uniqueReasons.length) return mergeResult('rejected', undefined, uniqueReasons, preparedRegions, input, explicitPolicy);
50
- const sourceText = applyMemberAdditions(headSourceText, preparedRegions);
51
- return mergeResult('merged', sourceText, [], preparedRegions, input, explicitPolicy);
110
+ return reasonCodes;
52
111
  }
53
112
 
54
113
  function normalizePolicyRegions(policy) {
@@ -125,7 +184,8 @@ function prepareRegion(input) {
125
184
  headMembers: headMembers.members,
126
185
  workerAddedKeys,
127
186
  headAddedKeys,
128
- workerAddedMembers: workerMembers.members.filter((member) => workerAddedKeys.includes(member.key))
187
+ workerAddedMembers: workerMembers.members.filter((member) => workerAddedKeys.includes(member.key)),
188
+ headAddedMembers: headMembers.members.filter((member) => headAddedKeys.includes(member.key))
129
189
  }
130
190
  };
131
191
  }
@@ -197,6 +257,9 @@ function regionReason(region, reason) {
197
257
  }
198
258
 
199
259
  export {
260
+ analyzeJsTsSafeMemberAdditions,
261
+ applyJsTsPreparedMemberAdditions,
200
262
  mergeJsTsSafeMemberAdditions,
263
+ neutralizeJsTsSafeMemberMergeSources,
201
264
  safeMergeJsTsMembers
202
265
  };
@@ -0,0 +1,170 @@
1
+ import {
2
+ JsTsSafeMergeConflictCodes,
3
+ JsTsSafeMergeGateIds,
4
+ JsTsSafeMergeStatuses,
5
+ jsTsSafeMergeGateOrder
6
+ } from './js-ts-safe-merge-constants.js';
7
+ import { safeMergeJsTsImportsAndDeclarations } from './js-ts-safe-merge.js';
8
+ import {
9
+ applyJsTsPreparedMemberAdditions,
10
+ neutralizeJsTsSafeMemberMergeSources
11
+ } from './js-ts-safe-member-merge.js';
12
+
13
+ function safeMergeJsTsSource(input = {}) {
14
+ if (!hasMemberMergePolicy(input)) return safeMergeJsTsImportsAndDeclarations(input);
15
+
16
+ const memberNeutralization = neutralizeJsTsSafeMemberMergeSources(input);
17
+ if (!memberNeutralization.ok) {
18
+ return composedBlockedResult(input, 'member-analysis', memberNeutralization.result, memberNeutralization.analysis);
19
+ }
20
+
21
+ const topLevelResult = safeMergeJsTsImportsAndDeclarations({
22
+ ...input,
23
+ baseSourceText: memberNeutralization.baseSourceText,
24
+ workerSourceText: memberNeutralization.workerSourceText,
25
+ headSourceText: memberNeutralization.headSourceText
26
+ });
27
+ if (topLevelResult.status !== JsTsSafeMergeStatuses.merged) {
28
+ return composedBlockedResult(input, 'top-level', topLevelResult, memberNeutralization.analysis);
29
+ }
30
+
31
+ const memberApplication = applyJsTsPreparedMemberAdditions(
32
+ topLevelResult.mergedSourceText,
33
+ memberNeutralization.analysis.preparedRegions,
34
+ ['head', 'worker']
35
+ );
36
+ if (memberApplication.reasonCodes.length) {
37
+ return composedMemberApplicationBlockedResult(input, topLevelResult, memberNeutralization.analysis, memberApplication.reasonCodes);
38
+ }
39
+
40
+ const memberAdditions = memberNeutralization.analysis.preparedRegions.reduce((total, region) => (
41
+ total + region.workerAddedKeys.length + region.headAddedKeys.length
42
+ ), 0);
43
+ return {
44
+ ...topLevelResult,
45
+ id: String(input.id ?? topLevelResult.id),
46
+ mergedSourceText: memberApplication.sourceText,
47
+ outputSourceText: memberApplication.sourceText,
48
+ summary: {
49
+ ...topLevelResult.summary,
50
+ memberRegions: memberNeutralization.analysis.preparedRegions.length,
51
+ memberAdditions,
52
+ composedPhases: 2
53
+ },
54
+ metadata: {
55
+ ...topLevelResult.metadata,
56
+ composed: {
57
+ phases: ['top-level', 'member'],
58
+ memberRegions: memberNeutralization.analysis.preparedRegions.map((region) => ({
59
+ kind: region.kind,
60
+ name: region.name,
61
+ regionKind: region.policy.regionKind,
62
+ workerAddedKeys: region.workerAddedKeys,
63
+ headAddedKeys: region.headAddedKeys
64
+ }))
65
+ }
66
+ }
67
+ };
68
+ }
69
+
70
+ function hasMemberMergePolicy(input) {
71
+ const policy = input.policy ?? input.mergePolicy ?? input;
72
+ const regions = Array.isArray(policy)
73
+ ? policy
74
+ : policy?.unorderedRegions
75
+ ?? policy?.unorderedMemberRegions
76
+ ?? policy?.safeList
77
+ ?? policy?.safeMembers
78
+ ?? [];
79
+ return Array.isArray(regions) && regions.length > 0;
80
+ }
81
+
82
+ function composedBlockedResult(input, phase, result, memberAnalysis) {
83
+ return {
84
+ kind: 'frontier.lang.jsTsSafeMerge',
85
+ version: 1,
86
+ schema: 'frontier.lang.jsTsSafeMerge.v1',
87
+ id: String(input.id ?? result.id ?? 'js_ts_safe_merge'),
88
+ status: JsTsSafeMergeStatuses.blocked,
89
+ sourcePath: input.sourcePath,
90
+ language: input.language ?? 'typescript',
91
+ conflicts: result.conflicts ?? [],
92
+ gates: result.gates ?? [],
93
+ admission: {
94
+ status: 'blocked',
95
+ action: 'human-review',
96
+ reviewRequired: true,
97
+ autoApplyCandidate: false,
98
+ autoMergeClaim: false,
99
+ semanticEquivalenceClaim: false,
100
+ reasonCodes: result.admission?.reasonCodes ?? result.reasonCodes ?? []
101
+ },
102
+ summary: {
103
+ importSpecifierAdditions: 0,
104
+ topLevelDeclarationAdditions: 0,
105
+ changedExistingDeclarations: result.summary?.changedExistingDeclarations ?? 0,
106
+ conflicts: result.conflicts?.length ?? result.summary?.conflicts ?? 0,
107
+ gatesPassed: result.gates?.filter((gate) => gate.status === 'passed').length ?? 0,
108
+ memberRegions: memberAnalysis?.preparedRegions?.length ?? 0,
109
+ memberAdditions: 0,
110
+ composedPhases: phase === 'top-level' ? 2 : 1
111
+ },
112
+ metadata: {
113
+ composed: {
114
+ phase,
115
+ sourceKind: result.kind
116
+ }
117
+ }
118
+ };
119
+ }
120
+
121
+ function composedMemberApplicationBlockedResult(input, topLevelResult, memberAnalysis, reasonCodes) {
122
+ const conflicts = reasonCodes.map((reason) => ({
123
+ code: JsTsSafeMergeConflictCodes.parserLedgerLoss,
124
+ gateId: JsTsSafeMergeGateIds.parseLedger,
125
+ message: 'Composed member application could not find a stable target container.',
126
+ side: 'merged',
127
+ sourcePath: input.sourcePath,
128
+ details: { reason }
129
+ }));
130
+ return {
131
+ ...topLevelResult,
132
+ status: JsTsSafeMergeStatuses.blocked,
133
+ mergedSourceText: undefined,
134
+ outputSourceText: undefined,
135
+ conflicts,
136
+ gates: jsTsSafeMergeGateOrder.map((id, index) => ({
137
+ id,
138
+ status: index === 0 ? 'blocked' : 'skipped',
139
+ reasonCodes: index === 0 ? [JsTsSafeMergeConflictCodes.parserLedgerLoss] : []
140
+ })),
141
+ admission: {
142
+ status: 'blocked',
143
+ action: 'human-review',
144
+ reviewRequired: true,
145
+ autoApplyCandidate: false,
146
+ autoMergeClaim: false,
147
+ semanticEquivalenceClaim: false,
148
+ reasonCodes: [JsTsSafeMergeConflictCodes.parserLedgerLoss]
149
+ },
150
+ summary: {
151
+ ...topLevelResult.summary,
152
+ conflicts: conflicts.length,
153
+ gatesPassed: 0,
154
+ memberRegions: memberAnalysis.preparedRegions.length,
155
+ memberAdditions: 0,
156
+ composedPhases: 2
157
+ },
158
+ metadata: {
159
+ ...topLevelResult.metadata,
160
+ composed: {
161
+ phase: 'member-application',
162
+ reasonCodes
163
+ }
164
+ }
165
+ };
166
+ }
167
+
168
+ export {
169
+ safeMergeJsTsSource
170
+ };
@@ -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.105",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",