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

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.
Files changed (124) hide show
  1. package/README.md +13 -0
  2. package/dist/declarations/bidirectional-target-change-source-edit.d.ts +30 -0
  3. package/dist/declarations/bidirectional-target-change.d.ts +10 -0
  4. package/dist/declarations/js-ts-safe-member-merge.d.ts +58 -0
  5. package/dist/declarations/js-ts-safe-merge.d.ts +120 -0
  6. package/dist/declarations/js-ts-semantic-conflict-sidecars.d.ts +235 -0
  7. package/dist/declarations/js-ts-semantic-merge-contracts.d.ts +287 -0
  8. package/dist/declarations/js-ts-semantic-merge.d.ts +4 -0
  9. package/dist/declarations/native-import-losses.d.ts +3 -0
  10. package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +12 -0
  11. package/dist/declarations/semantic-edit-script.d.ts +7 -4
  12. package/dist/declarations/semantic-patch-bundle-index.d.ts +45 -0
  13. package/dist/declarations/semantic-patch-bundle.d.ts +6 -4
  14. package/dist/declarations/semantic-sidecar-example.d.ts +18 -0
  15. package/dist/declarations/semantic-transform-identity.d.ts +3 -0
  16. package/dist/declarations/source-preservation.d.ts +72 -0
  17. package/dist/declarations/universal-capability.d.ts +4 -0
  18. package/dist/declarations/universal-conversion-artifacts.d.ts +61 -1
  19. package/dist/declarations/universal-conversion-compact-counts.d.ts +51 -0
  20. package/dist/declarations/universal-conversion-plan.d.ts +6 -1
  21. package/dist/declarations/universal-representation-coverage.d.ts +90 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.js +3 -0
  24. package/dist/internal/index-impl/bidirectionalExactSourceBackprojection.js +199 -0
  25. package/dist/internal/index-impl/bidirectionalSameLanguageSourceProjection.js +112 -0
  26. package/dist/internal/index-impl/bidirectionalSourceEditProjection.js +319 -0
  27. package/dist/internal/index-impl/bidirectionalSourceEditProjectionArtifacts.js +67 -0
  28. package/dist/internal/index-impl/bidirectionalTargetChangeRecordInternals.js +17 -5
  29. package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +58 -20
  30. package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +60 -7
  31. package/dist/internal/index-impl/createLightweightNativeImport.js +1 -0
  32. package/dist/internal/index-impl/createNativeSourcePreservation.js +28 -2
  33. package/dist/internal/index-impl/diffNativeSymbols.js +3 -3
  34. package/dist/internal/index-impl/nativeChangeProjectionSourceMapLinks.js +2 -0
  35. package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +43 -8
  36. package/dist/internal/index-impl/replaySemanticEditLineEndings.js +34 -0
  37. package/dist/internal/index-impl/replaySemanticEditProjection.js +39 -19
  38. package/dist/internal/index-impl/semanticEditBundleAdmission.js +7 -3
  39. package/dist/internal/index-impl/semanticEditBundleIndex.js +47 -1
  40. package/dist/internal/index-impl/semanticEditExplicitSourceReplacement.js +40 -0
  41. package/dist/internal/index-impl/semanticEditOperationCoverage.js +33 -3
  42. package/dist/internal/index-impl/semanticEditProjectionRecord.js +29 -0
  43. package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +39 -0
  44. package/dist/internal/index-impl/semanticEditReplaySourceReplacement.js +85 -0
  45. package/dist/internal/index-impl/semanticEditScripts.js +4 -0
  46. package/dist/internal/index-impl/semanticEditSourceRanges.js +27 -0
  47. package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +1 -0
  48. package/dist/internal/index-impl/semanticPatchBundleAdmission.js +41 -7
  49. package/dist/internal/index-impl/semanticPatchBundleRecords.js +16 -0
  50. package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +2 -0
  51. package/dist/internal/index-impl/semanticSidecarQuality.js +111 -0
  52. package/dist/internal/index-impl/semanticSourceEditDedupe.js +69 -9
  53. package/dist/internal/index-impl/semanticTransformIdentityRecords.js +85 -9
  54. package/dist/js-ts-safe-member-merge-result.js +158 -0
  55. package/dist/js-ts-safe-member-merge.js +202 -0
  56. package/dist/js-ts-safe-merge-analyze.js +279 -0
  57. package/dist/js-ts-safe-merge-constants.js +50 -0
  58. package/dist/js-ts-safe-merge-context.js +118 -0
  59. package/dist/js-ts-safe-merge-ledger-validation.js +92 -0
  60. package/dist/js-ts-safe-merge-ledger.js +85 -0
  61. package/dist/js-ts-safe-merge-parse-declarations.js +210 -0
  62. package/dist/js-ts-safe-merge-parse-statements.js +155 -0
  63. package/dist/js-ts-safe-merge-plan.js +190 -0
  64. package/dist/js-ts-safe-merge.js +175 -0
  65. package/dist/js-ts-semantic-conflict-sidecar-constants.js +77 -0
  66. package/dist/js-ts-semantic-conflict-sidecar-detectors.js +195 -0
  67. package/dist/js-ts-semantic-conflict-sidecar-normalize.js +203 -0
  68. package/dist/js-ts-semantic-conflict-sidecar-utils.js +190 -0
  69. package/dist/js-ts-semantic-conflict-sidecars.js +81 -0
  70. package/dist/js-ts-semantic-merge-contract-helpers.js +128 -0
  71. package/dist/js-ts-semantic-merge-contracts.js +217 -0
  72. package/dist/js-ts-semantic-merge-member-containers.js +100 -0
  73. package/dist/js-ts-semantic-merge-member-keys.js +142 -0
  74. package/dist/js-ts-semantic-merge-member-segments.js +185 -0
  75. package/dist/js-ts-semantic-merge-member-source.js +64 -0
  76. package/dist/js-ts-semantic-merge-member-utils.js +18 -0
  77. package/dist/js-ts-semantic-merge-parse.js +15 -0
  78. package/dist/js-ts-semantic-merge.js +21 -0
  79. package/dist/lightweight-dependency-effects.js +51 -0
  80. package/dist/lightweight-dependency-language.js +12 -1
  81. package/dist/lightweight-dependency-relations.js +14 -27
  82. package/dist/native-region-scanner-core.js +33 -1
  83. package/dist/native-region-scanner-csharp.js +151 -0
  84. package/dist/native-region-scanner-dart.js +91 -0
  85. package/dist/native-region-scanner-dynamic.js +21 -151
  86. package/dist/native-region-scanner-functional.js +40 -13
  87. package/dist/native-region-scanner-java.js +97 -0
  88. package/dist/native-region-scanner-js-class.js +100 -0
  89. package/dist/native-region-scanner-js-helpers.js +28 -86
  90. package/dist/native-region-scanner-js-imports.js +121 -1
  91. package/dist/native-region-scanner-js-nested.js +96 -8
  92. package/dist/native-region-scanner-js-structure.js +27 -0
  93. package/dist/native-region-scanner-js-types.js +99 -0
  94. package/dist/native-region-scanner-js.js +70 -118
  95. package/dist/native-region-scanner-kotlin.js +94 -0
  96. package/dist/native-region-scanner-main.js +15 -181
  97. package/dist/native-region-scanner-php.js +80 -0
  98. package/dist/native-region-scanner-python.js +62 -0
  99. package/dist/native-region-scanner-ruby.js +72 -0
  100. package/dist/native-region-scanner-scala.js +91 -0
  101. package/dist/native-region-scanner-spans.js +74 -0
  102. package/dist/native-region-scanner-swift.js +155 -0
  103. package/dist/native-region-scanner.js +14 -10
  104. package/dist/native-source-ledger-helpers.js +195 -0
  105. package/dist/native-source-ledger.js +306 -0
  106. package/dist/native-source-preservation-scanner.js +4 -0
  107. package/dist/semantic-import-callsite-regions.js +136 -0
  108. package/dist/semantic-import-effect-regions.js +283 -0
  109. package/dist/semantic-import-regions.js +11 -2
  110. package/dist/semantic-import-sidecar-entry.js +16 -2
  111. package/dist/semantic-import-sidecar-types.d.ts +2 -0
  112. package/dist/semantic-sidecar-example.js +68 -0
  113. package/dist/universal-capability-matrix.js +23 -0
  114. package/dist/universal-conversion-artifact-query.js +79 -2
  115. package/dist/universal-conversion-artifact-semantic-edit.js +103 -0
  116. package/dist/universal-conversion-artifact-summary.js +33 -1
  117. package/dist/universal-conversion-artifacts.js +13 -48
  118. package/dist/universal-conversion-plan-scoring.js +21 -1
  119. package/dist/universal-conversion-plan-summary.js +30 -0
  120. package/dist/universal-conversion-plan.js +25 -9
  121. package/dist/universal-conversion-route-metadata.js +96 -0
  122. package/dist/universal-conversion-route-operations.js +7 -0
  123. package/dist/universal-representation-coverage.js +193 -0
  124. package/package.json +1 -1
@@ -0,0 +1,202 @@
1
+ import {
2
+ applyMemberAdditions,
3
+ canonicalizeSourceBodies,
4
+ findContainer,
5
+ normalizeKind,
6
+ normalizeMemberText,
7
+ parseMembers,
8
+ uniqueStrings
9
+ } from './js-ts-semantic-merge-parse.js';
10
+ import { mergeResult } from './js-ts-safe-member-merge-result.js';
11
+
12
+ const NonSemanticRegionKinds = new Set(['property', 'type', 'config', 'content']);
13
+ const OrderSensitiveRegionKinds = new Set(['body', 'call', 'controlFlow', 'effect', 'import', 'mutation', 'route']);
14
+
15
+ function safeMergeJsTsMembers(input = {}) {
16
+ return mergeJsTsSafeMemberAdditions(input);
17
+ }
18
+
19
+ function mergeJsTsSafeMemberAdditions(input = {}) {
20
+ const baseSourceText = input.baseSourceText;
21
+ const workerSourceText = input.workerSourceText;
22
+ const headSourceText = input.headSourceText;
23
+ const reasonCodes = [];
24
+ if (typeof baseSourceText !== 'string') reasonCodes.push('missing-base-source-text');
25
+ if (typeof workerSourceText !== 'string') reasonCodes.push('missing-worker-source-text');
26
+ if (typeof headSourceText !== 'string') reasonCodes.push('missing-head-source-text');
27
+ const policyRegions = normalizePolicyRegions(input.policy ?? input.mergePolicy ?? input);
28
+ if (!policyRegions.length) reasonCodes.push('missing-unordered-region-policy');
29
+ const preparedRegions = [];
30
+ for (const region of policyRegions) {
31
+ const policyReasons = validatePolicyRegion(region);
32
+ reasonCodes.push(...policyReasons);
33
+ if (policyReasons.length) continue;
34
+ if (typeof baseSourceText !== 'string' || typeof workerSourceText !== 'string' || typeof headSourceText !== 'string') continue;
35
+ const prepared = prepareRegion({ region, baseSourceText, workerSourceText, headSourceText });
36
+ reasonCodes.push(...prepared.reasonCodes);
37
+ if (prepared.ok) preparedRegions.push(prepared.value);
38
+ }
39
+ if (typeof baseSourceText === 'string' && typeof workerSourceText === 'string' && preparedRegions.length) {
40
+ const canonicalWorker = canonicalizeSourceBodies(workerSourceText, preparedRegions, 'worker');
41
+ if (canonicalWorker !== baseSourceText) reasonCodes.push('non-policy-source-change:worker');
42
+ }
43
+ if (typeof baseSourceText === 'string' && typeof headSourceText === 'string' && preparedRegions.length) {
44
+ const canonicalHead = canonicalizeSourceBodies(headSourceText, preparedRegions, 'head');
45
+ if (canonicalHead !== baseSourceText) reasonCodes.push('non-policy-source-change:head');
46
+ }
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);
52
+ }
53
+
54
+ function normalizePolicyRegions(policy) {
55
+ const direct = Array.isArray(policy) ? policy : undefined;
56
+ const regions = direct
57
+ ?? policy?.unorderedRegions
58
+ ?? policy?.unorderedMemberRegions
59
+ ?? policy?.safeList
60
+ ?? policy?.safeMembers
61
+ ?? [];
62
+ return Array.isArray(regions) ? regions : [];
63
+ }
64
+
65
+ function validatePolicyRegion(region) {
66
+ const reasonCodes = [];
67
+ const kind = normalizeKind(region?.kind);
68
+ if (!kind || !['interface', 'type', 'class', 'object'].includes(kind)) reasonCodes.push('unsupported-region-kind');
69
+ if (!region?.name || typeof region.name !== 'string') reasonCodes.push('missing-region-name');
70
+ if (!regionDeclaresNonSemanticOrder(region)) reasonCodes.push('region-not-declared-non-semantic');
71
+ const regionKind = region?.regionKind;
72
+ if (regionKind && OrderSensitiveRegionKinds.has(regionKind)) reasonCodes.push(`order-sensitive-region-kind:${regionKind}`);
73
+ if (kind === 'object' && (!regionKind || !NonSemanticRegionKinds.has(regionKind))) reasonCodes.push('object-region-kind-not-safe-listed');
74
+ return reasonCodes;
75
+ }
76
+
77
+ function regionDeclaresNonSemanticOrder(region) {
78
+ return region?.order === 'non-semantic'
79
+ || region?.ordering === 'non-semantic'
80
+ || region?.nonSemanticOrder === true
81
+ || region?.orderSensitive === false;
82
+ }
83
+
84
+ function prepareRegion(input) {
85
+ const kind = normalizeKind(input.region.kind);
86
+ const base = findContainer(input.baseSourceText, input.region);
87
+ const worker = findContainer(input.workerSourceText, input.region);
88
+ const head = findContainer(input.headSourceText, input.region);
89
+ const reasonCodes = [];
90
+ for (const [side, match] of [['base', base], ['worker', worker], ['head', head]]) {
91
+ if (match.reasonCodes.length) reasonCodes.push(...match.reasonCodes.map((reason) => `${reason}:${side}:${kind}:${input.region.name}`));
92
+ }
93
+ if (reasonCodes.length) return { ok: false, reasonCodes };
94
+ const baseMembers = parseMembers(base.value.body, kind);
95
+ const workerMembers = parseMembers(worker.value.body, kind);
96
+ const headMembers = parseMembers(head.value.body, kind);
97
+ reasonCodes.push(...regionParseReasons(input.region, 'base', baseMembers));
98
+ reasonCodes.push(...regionParseReasons(input.region, 'worker', workerMembers));
99
+ reasonCodes.push(...regionParseReasons(input.region, 'head', headMembers));
100
+ reasonCodes.push(...duplicateReasons(input.region, 'base', baseMembers.members));
101
+ reasonCodes.push(...duplicateReasons(input.region, 'worker', workerMembers.members));
102
+ reasonCodes.push(...duplicateReasons(input.region, 'head', headMembers.members));
103
+ if (reasonCodes.length) return { ok: false, reasonCodes };
104
+ const baseByKey = membersByKey(baseMembers.members);
105
+ const workerByKey = membersByKey(workerMembers.members);
106
+ const headByKey = membersByKey(headMembers.members);
107
+ reasonCodes.push(...existingMemberReasons(input.region, 'worker', baseMembers.members, workerMembers.members, workerByKey));
108
+ reasonCodes.push(...existingMemberReasons(input.region, 'head', baseMembers.members, headMembers.members, headByKey));
109
+ const workerAddedKeys = workerMembers.members.map((member) => member.key).filter((key) => !baseByKey.has(key));
110
+ const headAddedKeys = headMembers.members.map((member) => member.key).filter((key) => !baseByKey.has(key));
111
+ reasonCodes.push(...duplicateAddedReasons(input.region, workerAddedKeys, workerByKey, headByKey));
112
+ if (reasonCodes.length) return { ok: false, reasonCodes };
113
+ return {
114
+ ok: true,
115
+ reasonCodes: [],
116
+ value: {
117
+ policy: input.region,
118
+ kind,
119
+ name: input.region.name,
120
+ base: base.value,
121
+ worker: worker.value,
122
+ head: head.value,
123
+ baseMembers: baseMembers.members,
124
+ workerMembers: workerMembers.members,
125
+ headMembers: headMembers.members,
126
+ workerAddedKeys,
127
+ headAddedKeys,
128
+ workerAddedMembers: workerMembers.members.filter((member) => workerAddedKeys.includes(member.key))
129
+ }
130
+ };
131
+ }
132
+
133
+ function regionParseReasons(region, side, parsed) {
134
+ return parsed.reasonCodes.map((reason) => regionReason(region, `${reason}:${side}`));
135
+ }
136
+
137
+ function duplicateReasons(region, side, members) {
138
+ const seen = new Map();
139
+ const duplicateGroups = new Map();
140
+ for (const member of members) {
141
+ const group = seen.get(member.key);
142
+ if (group) {
143
+ group.push(member);
144
+ duplicateGroups.set(member.key, group);
145
+ } else {
146
+ seen.set(member.key, [member]);
147
+ }
148
+ }
149
+ return [...duplicateGroups].map(([key, group]) => {
150
+ const reason = group.some(isOverloadLikeMember) ? `overload-collision:${side}:${key}` : `duplicate-key:${side}:${key}`;
151
+ return regionReason(region, reason);
152
+ });
153
+ }
154
+
155
+ function existingMemberReasons(region, side, baseMembers, sideMembers, sideByKey) {
156
+ const reasonCodes = [];
157
+ const sideBaseOrder = sideMembers.map((member) => member.key).filter((key) => baseMembers.some((baseMember) => baseMember.key === key));
158
+ const baseOrder = baseMembers.map((member) => member.key);
159
+ if (sideBaseOrder.join('\u0000') !== baseOrder.join('\u0000')) {
160
+ reasonCodes.push(regionReason(region, `existing-member-order-changed:${side}`));
161
+ }
162
+ for (const baseMember of baseMembers) {
163
+ const sideMember = sideByKey.get(baseMember.key);
164
+ if (!sideMember) {
165
+ reasonCodes.push(regionReason(region, `existing-member-removed:${side}:${baseMember.key}`));
166
+ continue;
167
+ }
168
+ if (normalizeMemberText(sideMember.text) !== normalizeMemberText(baseMember.text)) {
169
+ reasonCodes.push(regionReason(region, `existing-member-changed:${side}:${baseMember.key}`));
170
+ }
171
+ }
172
+ return reasonCodes;
173
+ }
174
+
175
+ function duplicateAddedReasons(region, workerAddedKeys, workerByKey, headByKey) {
176
+ const duplicateAddedKeys = workerAddedKeys.filter((key) => headByKey.has(key));
177
+ return duplicateAddedKeys.map((key) => {
178
+ const workerMember = workerByKey.get(key);
179
+ const headMember = headByKey.get(key);
180
+ const reason = isOverloadLikeMember(workerMember) || isOverloadLikeMember(headMember)
181
+ ? `overload-collision:worker-head:${key}`
182
+ : `duplicate-added-key:${key}`;
183
+ return regionReason(region, reason);
184
+ });
185
+ }
186
+
187
+ function isOverloadLikeMember(member) {
188
+ return member?.memberKind === 'method' || member?.memberKind === 'constructor';
189
+ }
190
+
191
+ function membersByKey(members) {
192
+ return new Map(members.map((member) => [member.key, member]));
193
+ }
194
+
195
+ function regionReason(region, reason) {
196
+ return `${reason}:${normalizeKind(region.kind)}:${region.name}`;
197
+ }
198
+
199
+ export {
200
+ mergeJsTsSafeMemberAdditions,
201
+ safeMergeJsTsMembers
202
+ };
@@ -0,0 +1,279 @@
1
+ import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds } from './js-ts-safe-merge-constants.js';
2
+ import { addConflict, arraysEqual, sameStatementText } from './js-ts-safe-merge-context.js';
3
+
4
+ export function analyzeVariantLedger(base, variant, baseIndex, side, context) {
5
+ const projectedBaseKeys = [];
6
+ const addedEntries = [];
7
+ const importAdditions = new Map();
8
+ const baseKeys = new Set(baseIndex.orderedKeys);
9
+
10
+ for (const entry of variant.entries) {
11
+ const baseEntry = baseIndex.entriesByKey.get(entry.key);
12
+ if (!baseEntry) {
13
+ if (entry.kind === 'import') {
14
+ addConflict(context, {
15
+ code: JsTsSafeMergeConflictCodes.newImportDeclaration,
16
+ gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
17
+ side,
18
+ message: `${side} source adds a new import declaration; only specifier additions to existing imports are accepted.`,
19
+ details: { key: entry.key, statement: entry.text.trim() }
20
+ });
21
+ } else {
22
+ addedEntries.push(entry);
23
+ }
24
+ continue;
25
+ }
26
+ projectedBaseKeys.push(entry.key);
27
+ if (entry.kind !== baseEntry.kind) {
28
+ addConflict(context, {
29
+ code: JsTsSafeMergeConflictCodes.parserLedgerLoss,
30
+ gateId: JsTsSafeMergeGateIds.parseLedger,
31
+ side,
32
+ message: `${side} source changes a base ledger entry kind.`,
33
+ details: { key: entry.key, baseKind: baseEntry.kind, sideKind: entry.kind }
34
+ });
35
+ continue;
36
+ }
37
+ if (entry.kind === 'import') {
38
+ const additions = analyzeImportStatementChange(baseEntry, entry, side, context);
39
+ if (additions.length) importAdditions.set(entry.key, additions);
40
+ } else if (!sameStatementText(baseEntry.text, entry.text)) {
41
+ const typeAliasConflict = baseEntry.declarationInfo?.declarationKind === 'type'
42
+ || entry.declarationInfo?.declarationKind === 'type';
43
+ addConflict(context, {
44
+ code: typeAliasConflict ? JsTsSafeMergeConflictCodes.typeAliasConflict : JsTsSafeMergeConflictCodes.changedExistingDeclaration,
45
+ gateId: JsTsSafeMergeGateIds.stableExistingDeclarations,
46
+ side,
47
+ message: typeAliasConflict
48
+ ? `${side} source changes an existing type alias body.`
49
+ : `${side} source changes an existing top-level declaration or export body.`,
50
+ details: { key: entry.key, name: entry.names?.[0], declarationKind: entry.declarationInfo?.declarationKind }
51
+ });
52
+ }
53
+ }
54
+
55
+ if (!arraysEqual(projectedBaseKeys, baseIndex.orderedKeys)) {
56
+ const code = base.entries.some((entry) => entry.kind === 'import' && entry.importInfo.sideEffectOnly)
57
+ ? JsTsSafeMergeConflictCodes.sideEffectImportReorder
58
+ : JsTsSafeMergeConflictCodes.topLevelOrderChanged;
59
+ addConflict(context, {
60
+ code,
61
+ gateId: JsTsSafeMergeGateIds.preserveBaseOrder,
62
+ side,
63
+ message: `${side} source changes the order or presence of base top-level entries.`,
64
+ details: {
65
+ expected: baseIndex.orderedKeys,
66
+ actual: projectedBaseKeys
67
+ }
68
+ });
69
+ }
70
+
71
+ return {
72
+ side,
73
+ addedEntries,
74
+ importAdditions,
75
+ baseKeys
76
+ };
77
+ }
78
+
79
+ function analyzeImportStatementChange(baseEntry, variantEntry, side, context) {
80
+ const baseImport = baseEntry.importInfo;
81
+ const variantImport = variantEntry.importInfo;
82
+ if (!sameImportShape(baseImport, variantImport)) {
83
+ addConflict(context, {
84
+ code: baseImport.sideEffectOnly ? JsTsSafeMergeConflictCodes.sideEffectImportReorder : JsTsSafeMergeConflictCodes.importShapeChanged,
85
+ gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
86
+ side,
87
+ message: `${side} source changes an existing import shape instead of adding named specifiers.`,
88
+ details: { key: baseEntry.key }
89
+ });
90
+ return [];
91
+ }
92
+ if (baseImport.sideEffectOnly) {
93
+ if (!sameStatementText(baseEntry.text, variantEntry.text)) {
94
+ addConflict(context, {
95
+ code: JsTsSafeMergeConflictCodes.sideEffectImportReorder,
96
+ gateId: JsTsSafeMergeGateIds.preserveBaseOrder,
97
+ side,
98
+ message: `${side} source changes a side-effect import.`,
99
+ details: { key: baseEntry.key }
100
+ });
101
+ }
102
+ return [];
103
+ }
104
+
105
+ const baseSpecifiers = baseImport.specifiers;
106
+ const variantSpecifiers = variantImport.specifiers;
107
+ if (variantSpecifiers.length < baseSpecifiers.length) {
108
+ addConflict(context, {
109
+ code: JsTsSafeMergeConflictCodes.importSpecifierRemoved,
110
+ gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
111
+ side,
112
+ message: `${side} source removes import specifiers.`,
113
+ details: { key: baseEntry.key }
114
+ });
115
+ return [];
116
+ }
117
+ for (let index = 0; index < baseSpecifiers.length; index += 1) {
118
+ if (variantSpecifiers[index]?.canonical !== baseSpecifiers[index].canonical) {
119
+ addConflict(context, {
120
+ code: JsTsSafeMergeConflictCodes.importSpecifierReordered,
121
+ gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
122
+ side,
123
+ message: `${side} source reorders or changes existing import specifiers.`,
124
+ details: {
125
+ key: baseEntry.key,
126
+ expected: baseSpecifiers.map((specifier) => specifier.canonical),
127
+ actual: variantSpecifiers.map((specifier) => specifier.canonical)
128
+ }
129
+ });
130
+ return [];
131
+ }
132
+ }
133
+ const additions = variantSpecifiers.slice(baseSpecifiers.length);
134
+ if (additions.length === 0 && !sameStatementText(baseEntry.text, variantEntry.text)) {
135
+ addConflict(context, {
136
+ code: JsTsSafeMergeConflictCodes.importFormattingChanged,
137
+ gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
138
+ side,
139
+ message: `${side} source changes import formatting without a specifier addition.`,
140
+ details: { key: baseEntry.key }
141
+ });
142
+ }
143
+ return additions;
144
+ }
145
+
146
+ function sameImportShape(left, right) {
147
+ return left.moduleSpecifier === right.moduleSpecifier
148
+ && left.typeOnly === right.typeOnly
149
+ && left.sideEffectOnly === right.sideEffectOnly
150
+ && left.defaultLocalName === right.defaultLocalName
151
+ && left.namespaceLocalName === right.namespaceLocalName;
152
+ }
153
+
154
+ export function validateIndependentAdditions(base, workerPlan, headPlan, context) {
155
+ const declarationNames = new Set();
156
+ for (const entry of base.entries.filter((item) => item.kind !== 'import')) {
157
+ for (const name of entry.names ?? []) declarationNames.add(`${entry.kind}:${name}`);
158
+ }
159
+ validateAddedEntryNames(workerPlan, declarationNames, context);
160
+ validateAddedEntryNames(headPlan, declarationNames, context);
161
+ validateCrossSideAddedNames(workerPlan, headPlan, context);
162
+ validateCrossSideImportAdditions(workerPlan, headPlan, context);
163
+ validateMergedImportAndDeclarationNames(base, workerPlan, headPlan, context);
164
+ }
165
+
166
+ function validateAddedEntryNames(plan, baseNames, context) {
167
+ for (const entry of plan.addedEntries) {
168
+ if (entry.kind !== 'declaration' && entry.kind !== 'export') {
169
+ addConflict(context, {
170
+ code: JsTsSafeMergeConflictCodes.parserLedgerLoss,
171
+ gateId: JsTsSafeMergeGateIds.independentTopLevelDeclarations,
172
+ side: plan.side,
173
+ message: `${plan.side} source adds an unsupported top-level entry.`,
174
+ details: { kind: entry.kind, statement: entry.text.trim() }
175
+ });
176
+ continue;
177
+ }
178
+ for (const name of entry.names ?? []) {
179
+ const nameKey = `${entry.kind}:${name}`;
180
+ if (baseNames.has(nameKey)) {
181
+ addConflict(context, {
182
+ code: JsTsSafeMergeConflictCodes.duplicateName,
183
+ gateId: JsTsSafeMergeGateIds.uniqueNames,
184
+ side: plan.side,
185
+ message: `${plan.side} source adds a duplicate top-level name.`,
186
+ details: { name, kind: entry.kind }
187
+ });
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ function validateCrossSideAddedNames(workerPlan, headPlan, context) {
194
+ const headEntriesByName = new Map();
195
+ for (const entry of headPlan.addedEntries) {
196
+ for (const name of entry.names ?? []) headEntriesByName.set(`${entry.kind}:${name}`, entry);
197
+ }
198
+ for (const entry of workerPlan.addedEntries) {
199
+ for (const name of entry.names ?? []) {
200
+ const nameKey = `${entry.kind}:${name}`;
201
+ const headEntry = headEntriesByName.get(nameKey);
202
+ if (headEntry) {
203
+ const typeAliasConflict = entry.declarationInfo?.declarationKind === 'type'
204
+ || headEntry.declarationInfo?.declarationKind === 'type';
205
+ addConflict(context, {
206
+ code: typeAliasConflict ? JsTsSafeMergeConflictCodes.typeAliasConflict : JsTsSafeMergeConflictCodes.duplicateName,
207
+ gateId: JsTsSafeMergeGateIds.uniqueNames,
208
+ side: 'worker',
209
+ message: typeAliasConflict
210
+ ? 'Worker and head add conflicting type aliases.'
211
+ : 'Worker and head add the same top-level name.',
212
+ details: { name, kind: entry.kind, workerDeclarationKind: entry.declarationInfo?.declarationKind, headDeclarationKind: headEntry.declarationInfo?.declarationKind }
213
+ });
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ function validateCrossSideImportAdditions(workerPlan, headPlan, context) {
220
+ for (const [key, workerAdditions] of workerPlan.importAdditions) {
221
+ const headAdditions = headPlan.importAdditions.get(key) ?? [];
222
+ const headLocalNames = new Set(headAdditions.map((specifier) => specifier.localName));
223
+ const headImportedNames = new Set(headAdditions.map((specifier) => `${specifier.typeOnly ? 'type:' : 'value:'}${specifier.importedName}`));
224
+ for (const specifier of workerAdditions) {
225
+ if (headLocalNames.has(specifier.localName) || headImportedNames.has(`${specifier.typeOnly ? 'type:' : 'value:'}${specifier.importedName}`)) {
226
+ addConflict(context, {
227
+ code: JsTsSafeMergeConflictCodes.duplicateName,
228
+ gateId: JsTsSafeMergeGateIds.uniqueNames,
229
+ side: 'worker',
230
+ message: 'Worker and head add duplicate import specifiers.',
231
+ details: { key, specifier: specifier.canonical }
232
+ });
233
+ }
234
+ }
235
+ }
236
+ }
237
+
238
+ function validateMergedImportAndDeclarationNames(base, workerPlan, headPlan, context) {
239
+ const topLevelBindingNames = new Set();
240
+ for (const entry of base.entries.filter((item) => item.kind !== 'import')) {
241
+ for (const name of entry.names ?? []) topLevelBindingNames.add(name);
242
+ }
243
+ for (const plan of [workerPlan, headPlan]) {
244
+ for (const entry of plan.addedEntries.filter((item) => item.kind !== 'import')) {
245
+ for (const name of entry.names ?? []) topLevelBindingNames.add(name);
246
+ }
247
+ }
248
+ const importLocalNames = new Set();
249
+ for (const entry of base.entries.filter((item) => item.kind === 'import')) {
250
+ const additions = [
251
+ ...(workerPlan.importAdditions.get(entry.key) ?? []),
252
+ ...(headPlan.importAdditions.get(entry.key) ?? [])
253
+ ];
254
+ for (const specifier of [
255
+ ...(entry.importInfo.defaultLocalName ? [{ localName: entry.importInfo.defaultLocalName, canonical: `default:${entry.importInfo.defaultLocalName}` }] : []),
256
+ ...(entry.importInfo.namespaceLocalName ? [{ localName: entry.importInfo.namespaceLocalName, canonical: `namespace:${entry.importInfo.namespaceLocalName}` }] : []),
257
+ ...entry.importInfo.specifiers,
258
+ ...additions
259
+ ]) {
260
+ if (importLocalNames.has(specifier.localName)) {
261
+ addConflict(context, {
262
+ code: JsTsSafeMergeConflictCodes.duplicateName,
263
+ gateId: JsTsSafeMergeGateIds.uniqueNames,
264
+ message: 'Merged imports would contain duplicate local names.',
265
+ details: { localName: specifier.localName, specifier: specifier.canonical }
266
+ });
267
+ }
268
+ if (topLevelBindingNames.has(specifier.localName)) {
269
+ addConflict(context, {
270
+ code: JsTsSafeMergeConflictCodes.duplicateName,
271
+ gateId: JsTsSafeMergeGateIds.uniqueNames,
272
+ message: 'Merged imports would duplicate a top-level declaration name.',
273
+ details: { localName: specifier.localName, specifier: specifier.canonical }
274
+ });
275
+ }
276
+ importLocalNames.add(specifier.localName);
277
+ }
278
+ }
279
+ }
@@ -0,0 +1,50 @@
1
+ export const JsTsSafeMergeStatuses = Object.freeze({
2
+ merged: 'merged',
3
+ blocked: 'blocked'
4
+ });
5
+
6
+ export const JsTsSafeMergeGateIds = Object.freeze({
7
+ parseLedger: 'parse-ledger',
8
+ preserveBaseOrder: 'preserve-base-order',
9
+ stableExistingDeclarations: 'stable-existing-declarations',
10
+ independentImportSpecifiers: 'independent-import-specifiers',
11
+ independentTopLevelDeclarations: 'independent-top-level-declarations',
12
+ uniqueNames: 'unique-names',
13
+ resolvedInsertionAnchors: 'resolved-insertion-anchors'
14
+ });
15
+
16
+ export const JsTsSafeMergeConflictCodes = Object.freeze({
17
+ invalidInput: 'invalid-input',
18
+ parserLedgerLoss: 'parser-ledger-loss',
19
+ malformedSyntax: 'malformed-syntax',
20
+ sideEffectImportReorder: 'side-effect-import-reorder',
21
+ topLevelOrderChanged: 'top-level-order-changed',
22
+ changedExistingDeclaration: 'changed-existing-declaration',
23
+ typeAliasConflict: 'type-alias-conflict',
24
+ importShapeChanged: 'import-shape-changed',
25
+ importSpecifierRemoved: 'import-specifier-removed',
26
+ importSpecifierReordered: 'import-specifier-reordered',
27
+ importFormattingChanged: 'import-formatting-changed',
28
+ newImportDeclaration: 'new-import-declaration',
29
+ duplicateName: 'duplicate-name',
30
+ computedKey: 'computed-key',
31
+ unsupportedDecorator: 'unsupported-decorator-merge-anchor',
32
+ unsupportedOverload: 'unsupported-overload-merge-anchor',
33
+ staleSourceHash: 'stale-source-hash',
34
+ missingSourceLedgerSpan: 'missing-source-ledger-span',
35
+ ambiguousInsertionPoint: 'ambiguous-insertion-point',
36
+ insertionAnchorMissing: 'insertion-anchor-missing'
37
+ });
38
+
39
+ export const jsTsSafeMergeGateOrder = Object.freeze([
40
+ JsTsSafeMergeGateIds.parseLedger,
41
+ JsTsSafeMergeGateIds.preserveBaseOrder,
42
+ JsTsSafeMergeGateIds.stableExistingDeclarations,
43
+ JsTsSafeMergeGateIds.independentImportSpecifiers,
44
+ JsTsSafeMergeGateIds.independentTopLevelDeclarations,
45
+ JsTsSafeMergeGateIds.uniqueNames,
46
+ JsTsSafeMergeGateIds.resolvedInsertionAnchors
47
+ ]);
48
+
49
+ const identifierPattern = '[A-Za-z_$][\\w$]*';
50
+ export const identifierRegExp = new RegExp(`^${identifierPattern}$`);
@@ -0,0 +1,118 @@
1
+ import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds, JsTsSafeMergeStatuses, jsTsSafeMergeGateOrder } from './js-ts-safe-merge-constants.js';
2
+
3
+ export function createMergeContext(input) {
4
+ return {
5
+ id: String(input.id ?? 'js_ts_safe_merge'),
6
+ sourcePath: input.sourcePath,
7
+ language: input.language ?? 'typescript',
8
+ conflicts: [],
9
+ blockedGateIds: new Set(),
10
+ gateReasonCodes: new Map()
11
+ };
12
+ }
13
+
14
+ export function blockedResult(context, ledgers = {}) {
15
+ const reasonCodes = uniqueStrings(context.conflicts.map((conflict) => conflict.code));
16
+ return {
17
+ kind: 'frontier.lang.jsTsSafeMerge',
18
+ version: 1,
19
+ schema: 'frontier.lang.jsTsSafeMerge.v1',
20
+ id: context.id,
21
+ status: JsTsSafeMergeStatuses.blocked,
22
+ sourcePath: context.sourcePath,
23
+ language: context.language,
24
+ conflicts: context.conflicts,
25
+ gates: gatesFor(context),
26
+ admission: {
27
+ status: 'blocked',
28
+ action: 'human-review',
29
+ reviewRequired: true,
30
+ autoApplyCandidate: false,
31
+ autoMergeClaim: false,
32
+ semanticEquivalenceClaim: false,
33
+ reasonCodes
34
+ },
35
+ summary: {
36
+ importSpecifierAdditions: 0,
37
+ topLevelDeclarationAdditions: 0,
38
+ changedExistingDeclarations: context.conflicts.filter((conflict) => conflict.code === JsTsSafeMergeConflictCodes.changedExistingDeclaration).length,
39
+ conflicts: context.conflicts.length,
40
+ gatesPassed: gatesFor(context).filter((gate) => gate.status === 'passed').length
41
+ },
42
+ metadata: {
43
+ ledgers: compactRecord({
44
+ base: ledgerSummary(ledgers.base),
45
+ worker: ledgerSummary(ledgers.worker),
46
+ head: ledgerSummary(ledgers.head),
47
+ merged: ledgerSummary(ledgers.merged)
48
+ })
49
+ }
50
+ };
51
+ }
52
+
53
+ export function addConflict(context, conflict) {
54
+ const gateId = conflict.gateId ?? JsTsSafeMergeGateIds.parseLedger;
55
+ const record = {
56
+ code: conflict.code,
57
+ gateId,
58
+ message: conflict.message,
59
+ side: conflict.side,
60
+ sourcePath: context.sourcePath,
61
+ details: compactRecord(conflict.details)
62
+ };
63
+ context.conflicts.push(record);
64
+ context.blockedGateIds.add(gateId);
65
+ const gateReasonCodes = context.gateReasonCodes.get(gateId) ?? [];
66
+ gateReasonCodes.push(conflict.code);
67
+ context.gateReasonCodes.set(gateId, uniqueStrings(gateReasonCodes));
68
+ }
69
+
70
+ export function gatesFor(context) {
71
+ let blockedSeen = false;
72
+ return jsTsSafeMergeGateOrder.map((id) => {
73
+ const blocked = context.blockedGateIds.has(id);
74
+ const status = blocked ? 'blocked' : blockedSeen ? 'skipped' : 'passed';
75
+ if (blocked) blockedSeen = true;
76
+ return {
77
+ id,
78
+ status,
79
+ reasonCodes: context.gateReasonCodes.get(id) ?? []
80
+ };
81
+ });
82
+ }
83
+
84
+ export function sameStatementText(left, right) {
85
+ return normalizeLineEndings(String(left ?? '').trim(), '\n') === normalizeLineEndings(String(right ?? '').trim(), '\n');
86
+ }
87
+
88
+ export function normalizeLineEndings(text, lineEnding) {
89
+ return String(text ?? '').replace(/\r\n?/g, '\n').replace(/\n/g, lineEnding);
90
+ }
91
+
92
+ export function detectLineEnding(text) {
93
+ return String(text ?? '').includes('\r\n') ? '\r\n' : '\n';
94
+ }
95
+
96
+ export function arraysEqual(left, right) {
97
+ if (left.length !== right.length) return false;
98
+ return left.every((value, index) => value === right[index]);
99
+ }
100
+
101
+ export function uniqueStrings(values) {
102
+ return [...new Set((values ?? []).filter((value) => typeof value === 'string' && value.length > 0))];
103
+ }
104
+
105
+ export function compactRecord(record) {
106
+ return Object.fromEntries(Object.entries(record ?? {}).filter(([, value]) => value !== undefined));
107
+ }
108
+
109
+ export function ledgerSummary(ledger) {
110
+ if (!ledger) return undefined;
111
+ return {
112
+ label: ledger.label,
113
+ entries: ledger.entries.length,
114
+ imports: ledger.entries.filter((entry) => entry.kind === 'import').length,
115
+ declarations: ledger.entries.filter((entry) => entry.kind === 'declaration').length,
116
+ exports: ledger.entries.filter((entry) => entry.kind === 'export').length
117
+ };
118
+ }