@shapeshift-labs/frontier-lang-compiler 0.2.102 → 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 (134) 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/native-project-admission-semantic-evidence.d.ts +34 -0
  11. package/dist/declarations/native-project-admission.d.ts +6 -10
  12. package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +12 -0
  13. package/dist/declarations/semantic-edit-script.d.ts +10 -4
  14. package/dist/declarations/semantic-patch-bundle-index.d.ts +45 -0
  15. package/dist/declarations/semantic-patch-bundle-overlaps.d.ts +1 -0
  16. package/dist/declarations/semantic-patch-bundle.d.ts +6 -4
  17. package/dist/declarations/semantic-sidecar-example.d.ts +18 -0
  18. package/dist/declarations/semantic-transform-identity.d.ts +3 -0
  19. package/dist/declarations/source-preservation.d.ts +72 -0
  20. package/dist/declarations/universal-capability.d.ts +4 -0
  21. package/dist/declarations/universal-conversion-artifacts.d.ts +61 -1
  22. package/dist/declarations/universal-conversion-compact-counts.d.ts +51 -0
  23. package/dist/declarations/universal-conversion-plan.d.ts +6 -1
  24. package/dist/declarations/universal-representation-coverage.d.ts +90 -0
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.js +3 -0
  27. package/dist/internal/index-impl/bidirectionalExactSourceBackprojection.js +199 -0
  28. package/dist/internal/index-impl/bidirectionalSameLanguageSourceProjection.js +112 -0
  29. package/dist/internal/index-impl/bidirectionalSourceEditProjection.js +319 -0
  30. package/dist/internal/index-impl/bidirectionalSourceEditProjectionArtifacts.js +67 -0
  31. package/dist/internal/index-impl/bidirectionalTargetChangeRecordInternals.js +17 -5
  32. package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +58 -20
  33. package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +60 -7
  34. package/dist/internal/index-impl/createLightweightNativeImport.js +1 -0
  35. package/dist/internal/index-impl/createNativeSourcePreservation.js +28 -2
  36. package/dist/internal/index-impl/createProjectImportAdmissionRecord.js +14 -2
  37. package/dist/internal/index-impl/diffNativeSymbols.js +82 -1
  38. package/dist/internal/index-impl/nativeChangeProjectionSourceMapLinks.js +2 -0
  39. package/dist/internal/index-impl/projectImportAdmissionImportEvidence.js +1 -1
  40. package/dist/internal/index-impl/projectImportAdmissionSemanticWarnings.js +178 -0
  41. package/dist/internal/index-impl/projectImportAdmissionSummaries.js +22 -3
  42. package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +54 -69
  43. package/dist/internal/index-impl/replaySemanticEditLineEndings.js +34 -0
  44. package/dist/internal/index-impl/replaySemanticEditProjection.js +78 -78
  45. package/dist/internal/index-impl/semanticEditBundleAdmission.js +7 -3
  46. package/dist/internal/index-impl/semanticEditBundleIndex.js +47 -1
  47. package/dist/internal/index-impl/semanticEditExplicitSourceReplacement.js +40 -0
  48. package/dist/internal/index-impl/semanticEditImportProjection.js +53 -0
  49. package/dist/internal/index-impl/semanticEditOperationCoverage.js +33 -3
  50. package/dist/internal/index-impl/semanticEditProjectionRecord.js +108 -0
  51. package/dist/internal/index-impl/semanticEditReplayAnchors.js +63 -0
  52. package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +39 -0
  53. package/dist/internal/index-impl/semanticEditReplaySourceReplacement.js +85 -0
  54. package/dist/internal/index-impl/semanticEditScripts.js +4 -0
  55. package/dist/internal/index-impl/semanticEditSourceRanges.js +32 -0
  56. package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +1 -0
  57. package/dist/internal/index-impl/semanticPatchBundleAdmission.js +92 -9
  58. package/dist/internal/index-impl/semanticPatchBundleOverlaps.js +33 -16
  59. package/dist/internal/index-impl/semanticPatchBundleRecords.js +16 -0
  60. package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +2 -0
  61. package/dist/internal/index-impl/semanticSidecarQuality.js +111 -0
  62. package/dist/internal/index-impl/semanticSourceEditDedupe.js +69 -9
  63. package/dist/internal/index-impl/semanticTransformIdentityRecords.js +85 -9
  64. package/dist/js-ts-safe-member-merge-result.js +158 -0
  65. package/dist/js-ts-safe-member-merge.js +202 -0
  66. package/dist/js-ts-safe-merge-analyze.js +279 -0
  67. package/dist/js-ts-safe-merge-constants.js +50 -0
  68. package/dist/js-ts-safe-merge-context.js +118 -0
  69. package/dist/js-ts-safe-merge-ledger-validation.js +92 -0
  70. package/dist/js-ts-safe-merge-ledger.js +85 -0
  71. package/dist/js-ts-safe-merge-parse-declarations.js +210 -0
  72. package/dist/js-ts-safe-merge-parse-statements.js +155 -0
  73. package/dist/js-ts-safe-merge-plan.js +190 -0
  74. package/dist/js-ts-safe-merge.js +175 -0
  75. package/dist/js-ts-semantic-conflict-sidecar-constants.js +77 -0
  76. package/dist/js-ts-semantic-conflict-sidecar-detectors.js +195 -0
  77. package/dist/js-ts-semantic-conflict-sidecar-normalize.js +203 -0
  78. package/dist/js-ts-semantic-conflict-sidecar-utils.js +190 -0
  79. package/dist/js-ts-semantic-conflict-sidecars.js +81 -0
  80. package/dist/js-ts-semantic-merge-contract-helpers.js +128 -0
  81. package/dist/js-ts-semantic-merge-contracts.js +217 -0
  82. package/dist/js-ts-semantic-merge-member-containers.js +100 -0
  83. package/dist/js-ts-semantic-merge-member-keys.js +142 -0
  84. package/dist/js-ts-semantic-merge-member-segments.js +185 -0
  85. package/dist/js-ts-semantic-merge-member-source.js +64 -0
  86. package/dist/js-ts-semantic-merge-member-utils.js +18 -0
  87. package/dist/js-ts-semantic-merge-parse.js +15 -0
  88. package/dist/js-ts-semantic-merge.js +21 -0
  89. package/dist/lightweight-dependency-effects.js +51 -0
  90. package/dist/lightweight-dependency-language.js +12 -1
  91. package/dist/lightweight-dependency-relations.js +14 -27
  92. package/dist/native-region-scanner-core.js +33 -1
  93. package/dist/native-region-scanner-csharp.js +151 -0
  94. package/dist/native-region-scanner-dart.js +91 -0
  95. package/dist/native-region-scanner-dynamic.js +21 -151
  96. package/dist/native-region-scanner-functional.js +40 -13
  97. package/dist/native-region-scanner-java.js +97 -0
  98. package/dist/native-region-scanner-js-class.js +100 -0
  99. package/dist/native-region-scanner-js-helpers.js +28 -86
  100. package/dist/native-region-scanner-js-imports.js +121 -1
  101. package/dist/native-region-scanner-js-nested.js +96 -8
  102. package/dist/native-region-scanner-js-structure.js +27 -0
  103. package/dist/native-region-scanner-js-types.js +99 -0
  104. package/dist/native-region-scanner-js.js +70 -118
  105. package/dist/native-region-scanner-kotlin.js +94 -0
  106. package/dist/native-region-scanner-main.js +15 -181
  107. package/dist/native-region-scanner-php.js +80 -0
  108. package/dist/native-region-scanner-python.js +62 -0
  109. package/dist/native-region-scanner-ruby.js +72 -0
  110. package/dist/native-region-scanner-scala.js +91 -0
  111. package/dist/native-region-scanner-spans.js +74 -0
  112. package/dist/native-region-scanner-swift.js +155 -0
  113. package/dist/native-region-scanner.js +14 -10
  114. package/dist/native-source-ledger-helpers.js +195 -0
  115. package/dist/native-source-ledger.js +306 -0
  116. package/dist/native-source-preservation-scanner.js +4 -0
  117. package/dist/semantic-import-callsite-regions.js +136 -0
  118. package/dist/semantic-import-effect-regions.js +283 -0
  119. package/dist/semantic-import-regions.js +11 -2
  120. package/dist/semantic-import-sidecar-entry.js +16 -2
  121. package/dist/semantic-import-sidecar-types.d.ts +2 -0
  122. package/dist/semantic-sidecar-example.js +68 -0
  123. package/dist/universal-capability-matrix.js +23 -0
  124. package/dist/universal-conversion-artifact-query.js +79 -2
  125. package/dist/universal-conversion-artifact-semantic-edit.js +103 -0
  126. package/dist/universal-conversion-artifact-summary.js +33 -1
  127. package/dist/universal-conversion-artifacts.js +13 -48
  128. package/dist/universal-conversion-plan-scoring.js +21 -1
  129. package/dist/universal-conversion-plan-summary.js +30 -0
  130. package/dist/universal-conversion-plan.js +25 -9
  131. package/dist/universal-conversion-route-metadata.js +96 -0
  132. package/dist/universal-conversion-route-operations.js +7 -0
  133. package/dist/universal-representation-coverage.js +193 -0
  134. package/package.json +1 -1
@@ -0,0 +1,40 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { spanOffsets } from './semanticEditSourceRanges.js';
3
+
4
+ export function explicitSourceReplacementEditForOperation(operation, identity, headSourceText, order) {
5
+ const backprojection = operation.metadata?.sourceBackprojection;
6
+ if (backprojection?.mode !== 'cross-language-explicit-source-replacement') return undefined;
7
+ const replacement = backprojection.sourceReplacementText;
8
+ const range = spanOffsets(headSourceText, backprojection.sourceEditSpan ?? operation.spans?.head ?? operation.spans?.base);
9
+ const anchorRange = spanOffsets(headSourceText, operation.anchor?.sourceSpan);
10
+ const reasons = [];
11
+ if (typeof replacement !== 'string') reasons.push(`source-replacement-text-missing:${operation.id}`);
12
+ if (!range) reasons.push(`head-span-not-resolvable:${operation.id}`);
13
+ if (reasons.length) return { ok: false, reasonCodes: reasons };
14
+ const current = headSourceText.slice(range.start, range.end);
15
+ const currentHash = hashSemanticValue(current);
16
+ const replacementHash = hashSemanticValue(replacement);
17
+ const expectedCurrentHash = backprojection.sourceEditTextHash ?? operation.hashes?.headTextHash ?? operation.hashes?.baseTextHash;
18
+ if (expectedCurrentHash && currentHash !== expectedCurrentHash) reasons.push(`head-span-hash-mismatch:${operation.id}`);
19
+ if (backprojection.sourceReplacementTextHash && replacementHash !== backprojection.sourceReplacementTextHash) {
20
+ reasons.push(`source-replacement-text-hash-mismatch:${operation.id}`);
21
+ }
22
+ if (reasons.length) return { ok: false, reasonCodes: reasons };
23
+ return {
24
+ ok: true,
25
+ value: {
26
+ operationId: operation.id,
27
+ order,
28
+ ...identity,
29
+ editKind: 'replace',
30
+ sourceRangeKind: 'cross-language-explicit-source-replacement',
31
+ start: range.start,
32
+ end: range.end,
33
+ headAnchorStart: anchorRange?.start,
34
+ headAnchorEnd: anchorRange?.end,
35
+ replacement,
36
+ replacementSpanText: replacement,
37
+ current
38
+ }
39
+ };
40
+ }
@@ -0,0 +1,53 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { spanOffsets } from './semanticEditSourceRanges.js';
3
+
4
+ export function alreadyAppliedImportEditForOperation(operation, identity, spanText, headSourceText, workerOffsets, order, context) {
5
+ if (!isAddImportOperation(operation) || typeof headSourceText !== 'string') return undefined;
6
+ const match = findHeadImportSymbol(operation, context.headSymbols);
7
+ const range = spanOffsets(headSourceText, match?.symbol?.sourceSpan);
8
+ if (!range) return undefined;
9
+ const current = headSourceText.slice(range.start, range.end);
10
+ if (!headImportMatchesOperation(operation, spanText, current, match.symbol)) return undefined;
11
+ return {
12
+ operationId: operation.id,
13
+ order,
14
+ ...identity,
15
+ start: range.start,
16
+ end: range.end,
17
+ workerStart: workerOffsets.start,
18
+ workerEnd: workerOffsets.end,
19
+ replacement: current,
20
+ replacementSpanText: spanText,
21
+ current,
22
+ alreadyApplied: true
23
+ };
24
+ }
25
+
26
+ function findHeadImportSymbol(operation, symbols) {
27
+ const symbolList = Array.isArray(symbols) ? symbols : [];
28
+ const exactKeys = [
29
+ operation.anchor?.key,
30
+ operation.anchor?.symbolId,
31
+ operation.insertion?.insertedSymbolId
32
+ ].filter(Boolean);
33
+ const exact = symbolList.find((symbol) => [symbol.ownershipKey, symbol.key, symbol.id].some((key) => key && exactKeys.includes(key)));
34
+ if (exact) return { symbol: exact, exact: true };
35
+ const name = operation.insertion?.insertedSymbolName ?? operation.anchor?.symbolName;
36
+ const kind = operation.insertion?.insertedSymbolKind ?? operation.anchor?.symbolKind;
37
+ const semantic = symbolList.find((symbol) => symbol.name === name && (!kind || symbol.kind === kind));
38
+ return semantic ? { symbol: semantic, exact: false } : undefined;
39
+ }
40
+
41
+ function headImportMatchesOperation(operation, spanText, current, symbol) {
42
+ const workerTextHash = operation.hashes?.workerTextHash ?? hashSemanticValue(spanText);
43
+ const workerSpanHash = operation.hashes?.workerSpanHash ?? workerTextHash;
44
+ const currentHash = hashSemanticValue(current);
45
+ if ([workerTextHash, workerSpanHash].includes(currentHash)) return true;
46
+ if ([workerTextHash, workerSpanHash].includes(symbol?.spanHash)) return true;
47
+ const signatureHash = operation.hashes?.afterSignatureHash;
48
+ return Boolean(signatureHash && symbol?.signatureHash === signatureHash);
49
+ }
50
+
51
+ function isAddImportOperation(operation) {
52
+ return operation?.kind === 'addImport' || (operation?.changeKind === 'added' && operation?.anchor?.regionKind === 'import');
53
+ }
@@ -7,14 +7,16 @@ export function markCoveredSemanticEditOperations(operations, context) {
7
7
  worker: nativeImportSourceText(context.worker)
8
8
  };
9
9
  return (operations ?? []).map((operation) => {
10
- const coveredBy = coveredByChildOperations(operation, operations, sourceText);
10
+ const childCoverage = coveredByChildOperations(operation, operations, sourceText);
11
+ const parentCoverage = childCoverage.length ? [] : coveredByParentOperations(operation, operations, sourceText);
12
+ const coveredBy = childCoverage.length ? childCoverage : parentCoverage;
11
13
  if (!coveredBy.length) return operation;
12
14
  return {
13
15
  ...operation,
14
16
  status: 'covered',
15
17
  readiness: 'ready',
16
18
  confidence: Math.max(operation.confidence ?? 0, 0.82),
17
- reasonCodes: uniqueStrings([...(operation.reasonCodes ?? []), 'container-covered-by-child-edits']),
19
+ reasonCodes: uniqueStrings([...(operation.reasonCodes ?? []), coverageReason(childCoverage)]),
18
20
  evidenceIds: uniqueStrings(operation.evidenceIds ?? []),
19
21
  metadata: {
20
22
  ...(operation.metadata ?? {}),
@@ -24,6 +26,10 @@ export function markCoveredSemanticEditOperations(operations, context) {
24
26
  });
25
27
  }
26
28
 
29
+ function coverageReason(childCoverage) {
30
+ return childCoverage.length ? 'container-covered-by-child-edits' : 'child-covered-by-container-edit';
31
+ }
32
+
27
33
  function coveredByChildOperations(container, operations, sourceText) {
28
34
  if (!isCoverableContainer(container)) return [];
29
35
  const containerBase = spanOffsets(sourceText.base, container.spans?.base);
@@ -43,7 +49,31 @@ function isCoverableContainer(operation) {
43
49
  if (operation.changeKind !== 'modified') return false;
44
50
  if (!operation.spans?.base || !operation.spans?.worker) return false;
45
51
  const kind = String(operation.anchor?.regionKind ?? operation.regionKind ?? '');
46
- return kind === 'type' || kind === 'config' || kind === 'content' || kind === 'route' || kind === 'property';
52
+ return ['type', 'config', 'content', 'route', 'property', 'controlFlow', 'effect', 'mutation'].includes(kind);
53
+ }
54
+
55
+ function coveredByParentOperations(child, operations, sourceText) {
56
+ if (child.changeKind !== 'added' && child.changeKind !== 'removed') return [];
57
+ if (!isSemanticFactRegion(child)) return [];
58
+ const side = child.changeKind === 'removed' ? 'base' : 'worker';
59
+ const childRange = spanOffsets(sourceText[side], child.spans?.[side]);
60
+ if (!childRange) return [];
61
+ return (operations ?? []).filter((parent) => parent.id !== child.id
62
+ && parent.changeKind === child.changeKind
63
+ && ['portable', 'already-applied'].includes(parent.status)
64
+ && parent.anchor?.regionKind === 'body'
65
+ && sameSourcePath(parent, child)
66
+ && contained(childRange, spanOffsets(sourceText[side], parent.spans?.[side])));
67
+ }
68
+
69
+ function isSemanticFactRegion(operation) {
70
+ return ['controlFlow', 'effect', 'mutation', 'call'].includes(String(operation.anchor?.regionKind ?? ''));
71
+ }
72
+
73
+ function sameSourcePath(left, right) {
74
+ const leftPath = left.anchor?.sourcePath ?? left.insertion?.sourcePath;
75
+ const rightPath = right.anchor?.sourcePath ?? right.insertion?.sourcePath;
76
+ return !leftPath || !rightPath || leftPath === rightPath;
47
77
  }
48
78
 
49
79
  function childEdit(operation, sourceText, containerBase) {
@@ -0,0 +1,108 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { semanticEditIdentityFields } from './semanticEditIdentityRecords.js';
3
+
4
+ export function projectionEditRecord(edit) {
5
+ const deletedTextHash = hashSemanticValue(edit.current);
6
+ const replacementTextHash = hashSemanticValue(edit.replacement);
7
+ const replacementSpanText = edit.replacementSpanText ?? edit.replacement;
8
+ const identity = semanticEditIdentityFields(edit);
9
+ const sourceIdentity = sourceIdentityAnchorFields(edit);
10
+ return compactRecord({
11
+ operationId: edit.operationId,
12
+ status: edit.alreadyApplied ? 'already-applied' : 'applied',
13
+ kind: edit.kind,
14
+ editKind: edit.editKind,
15
+ changeKind: edit.changeKind,
16
+ anchorKey: edit.anchorKey,
17
+ conflictKey: edit.conflictKey,
18
+ regionId: edit.regionId,
19
+ regionKind: edit.regionKind,
20
+ sourcePath: edit.sourcePath,
21
+ originalSourcePath: edit.originalSourcePath,
22
+ targetAnchorKey: edit.targetAnchorKey,
23
+ targetSourcePath: edit.targetSourcePath,
24
+ targetSymbolName: edit.targetSymbolName,
25
+ targetSymbolKind: edit.targetSymbolKind,
26
+ symbolId: edit.symbolId,
27
+ symbolName: edit.symbolName,
28
+ symbolKind: edit.symbolKind,
29
+ ...identity,
30
+ ...sourceIdentity,
31
+ operationContentHash: edit.operationContentHash,
32
+ editContentHash: hashSemanticValue(compactRecord({
33
+ semanticIdentityHash: identity.semanticIdentityHash,
34
+ sourceIdentityHash: identity.sourceIdentityHash,
35
+ sourceIdentityStatus: sourceIdentity.sourceIdentityStatus,
36
+ sourceIdentityAnchorKey: sourceIdentity.sourceIdentityAnchorKey,
37
+ targetIdentityAnchorKey: sourceIdentity.targetIdentityAnchorKey,
38
+ sourceRangeKind: edit.sourceRangeKind,
39
+ deletedTextHash,
40
+ replacementTextHash,
41
+ status: edit.alreadyApplied ? 'already-applied' : 'applied'
42
+ })),
43
+ sourceRangeKind: edit.sourceRangeKind,
44
+ headStart: edit.start,
45
+ headEnd: edit.end,
46
+ workerStart: edit.workerStart,
47
+ workerEnd: edit.workerEnd,
48
+ editOrder: edit.order,
49
+ headAnchorStart: edit.headAnchorStart,
50
+ headAnchorEnd: edit.headAnchorEnd,
51
+ workerAnchorStart: edit.workerAnchorStart,
52
+ workerAnchorEnd: edit.workerAnchorEnd,
53
+ deletedBytes: edit.current.length,
54
+ replacementBytes: edit.replacement.length,
55
+ deletedTextHash,
56
+ replacementTextHash,
57
+ deletedText: deletedTextForEdit(edit),
58
+ deletedTextLineEndingStableHash: lineEndingStableTextHash(edit.current),
59
+ replacementTextLineEndingStableHash: lineEndingStableTextHash(edit.replacement),
60
+ anchorDeletedTextHash: edit.anchorDeletedTextHash,
61
+ anchorReplacementTextHash: edit.anchorReplacementTextHash,
62
+ replacementSpanTextHash: hashSemanticValue(replacementSpanText),
63
+ replacementSpanTextLineEndingStableHash: lineEndingStableTextHash(replacementSpanText),
64
+ insertionMode: edit.insertion?.mode,
65
+ insertionAnchorKey: edit.insertion?.anchorKey,
66
+ insertionAnchorSymbolName: edit.insertion?.anchorSymbolName,
67
+ insertionAnchorSymbolKind: edit.insertion?.anchorSymbolKind,
68
+ insertionAnchorCandidates: edit.insertion?.anchorCandidates,
69
+ replacementText: edit.replacement
70
+ });
71
+ }
72
+
73
+ function deletedTextForEdit(edit) {
74
+ return edit.sourceRangeKind === 'cross-language-explicit-source-replacement' ? edit.current : undefined;
75
+ }
76
+
77
+ function sourceIdentityAnchorFields(edit) {
78
+ const sourceIdentityAnchorKey = edit.sourceIdentityAnchorKey ?? edit.anchorKey;
79
+ const targetIdentityAnchorKey = edit.targetIdentityAnchorKey ?? edit.targetAnchorKey ?? sourceIdentityAnchorKey;
80
+ const sourceIdentitySourcePath = edit.sourceIdentitySourcePath ?? edit.originalSourcePath ?? edit.sourcePath;
81
+ const targetIdentitySourcePath = edit.targetIdentitySourcePath ?? edit.targetSourcePath ?? edit.sourcePath;
82
+ const moved = Boolean(
83
+ (sourceIdentityAnchorKey && targetIdentityAnchorKey && sourceIdentityAnchorKey !== targetIdentityAnchorKey)
84
+ || (sourceIdentitySourcePath && targetIdentitySourcePath && sourceIdentitySourcePath !== targetIdentitySourcePath)
85
+ );
86
+ return compactRecord({
87
+ sourceIdentityStatus: edit.sourceIdentityStatus ?? (moved ? 'moved-source' : 'same-source'),
88
+ sourceIdentityAnchorKey,
89
+ targetIdentityAnchorKey,
90
+ sourceIdentitySourcePath,
91
+ targetIdentitySourcePath
92
+ });
93
+ }
94
+
95
+ function lineEndingStableTextHash(value) {
96
+ const normalized = lineEndingStableText(value);
97
+ return normalized === undefined ? undefined : hashSemanticValue(normalized);
98
+ }
99
+
100
+ function lineEndingStableText(value) {
101
+ if (typeof value !== 'string') return undefined;
102
+ const normalized = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
103
+ return normalized.length > 1 && normalized.endsWith('\n') ? normalized.slice(0, -1) : normalized;
104
+ }
105
+
106
+ function compactRecord(value) {
107
+ return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
108
+ }
@@ -0,0 +1,63 @@
1
+ import { afterLineOffset, spanOffsets } from './semanticEditSourceRanges.js';
2
+
3
+ export function findCurrentSymbol(edit, symbols) {
4
+ const exact = symbols.find((symbol) => [symbol.ownershipKey, symbol.key, symbol.id].some((key) => key && [
5
+ edit.anchorKey,
6
+ edit.targetAnchorKey,
7
+ edit.symbolId
8
+ ].includes(key)));
9
+ if (exact) return exact;
10
+ const name = edit.targetSymbolName ?? edit.symbolName;
11
+ const kind = edit.targetSymbolKind ?? edit.symbolKind;
12
+ return symbols.find((symbol) => symbol.name === name && (!kind || symbol.kind === kind));
13
+ }
14
+
15
+ export function findInsertionAnchor(edit, symbols) {
16
+ for (const candidate of insertionAnchorCandidates(edit)) {
17
+ const symbol = findInsertionAnchorSymbol(candidate, symbols);
18
+ if (symbol) return { candidate, symbol };
19
+ }
20
+ return undefined;
21
+ }
22
+
23
+ export function insertionAnchorCandidates(edit) {
24
+ const primary = {
25
+ mode: edit.insertionMode,
26
+ anchorKey: edit.insertionAnchorKey,
27
+ anchorSymbolName: edit.insertionAnchorSymbolName,
28
+ anchorSymbolKind: edit.insertionAnchorSymbolKind
29
+ };
30
+ const seen = new Set();
31
+ const result = [];
32
+ for (const candidate of [primary, ...(Array.isArray(edit.insertionAnchorCandidates) ? edit.insertionAnchorCandidates : [])]) {
33
+ if (!candidate || (candidate.mode !== 'before' && candidate.mode !== 'after')) continue;
34
+ const key = [candidate.mode, candidate.anchorKey, candidate.anchorSymbolId, candidate.anchorSymbolName, candidate.anchorSymbolKind].join('\0');
35
+ if (seen.has(key)) continue;
36
+ seen.add(key);
37
+ result.push(candidate);
38
+ }
39
+ return result;
40
+ }
41
+
42
+ export function hasSymbolAnchorIdentity(candidate) {
43
+ return Boolean(candidate.anchorKey || candidate.anchorSymbolId || candidate.anchorSymbolName);
44
+ }
45
+
46
+ export function insertionRange(edit, candidate, anchor, sourceText) {
47
+ if (edit.insertionMode === 'file-start') return { start: 0, end: 0 };
48
+ if (edit.insertionMode === 'file-end') return { start: sourceText.length, end: sourceText.length };
49
+ const mode = candidate?.mode ?? edit.insertionMode;
50
+ const anchorRange = spanOffsets(sourceText, anchor?.sourceSpan);
51
+ if (!anchorRange) return undefined;
52
+ if (mode === 'before') return { start: anchorRange.start, end: anchorRange.start };
53
+ if (mode === 'after') {
54
+ return { start: afterLineOffset(sourceText, anchorRange.end), end: afterLineOffset(sourceText, anchorRange.end) };
55
+ }
56
+ return undefined;
57
+ }
58
+
59
+ function findInsertionAnchorSymbol(candidate, symbols) {
60
+ const keys = [candidate.anchorKey, candidate.anchorSymbolId].filter(Boolean);
61
+ return symbols.find((symbol) => [symbol.ownershipKey, symbol.key, symbol.id].some((key) => key && keys.includes(key)))
62
+ ?? symbols.find((symbol) => symbol.name === candidate.anchorSymbolName && (!candidate.anchorSymbolKind || symbol.kind === candidate.anchorSymbolKind));
63
+ }
@@ -8,6 +8,7 @@ export function replayEditDiagnostics(edit, status, range, reasonCodes, sourceTe
8
8
  status,
9
9
  operationId: edit.operationId,
10
10
  sourcePath: edit.targetSourcePath ?? edit.sourcePath,
11
+ ...sourceIdentityDiagnosticContext(edit),
11
12
  symbolName: edit.targetSymbolName ?? edit.symbolName,
12
13
  symbolKind: edit.targetSymbolKind ?? edit.symbolKind,
13
14
  editKind: edit.editKind,
@@ -64,6 +65,7 @@ function appendOverlapDiagnostic(overlapDiagnostics, edit, code, operationIds) {
64
65
  status: 'conflict',
65
66
  operationId: edit.operationId,
66
67
  sourcePath: edit.sourcePath,
68
+ ...sourceIdentityDiagnosticContext(edit),
67
69
  symbolName: edit.symbolName,
68
70
  symbolKind: edit.symbolKind,
69
71
  editKind: edit.editKind,
@@ -93,6 +95,18 @@ function replayDiagnostic(code, context) {
93
95
  status: context.status,
94
96
  operationId: context.operationId,
95
97
  sourcePath: context.sourcePath,
98
+ originalSourcePath: context.originalSourcePath,
99
+ targetSourcePath: context.targetSourcePath,
100
+ anchorKey: context.anchorKey,
101
+ targetAnchorKey: context.targetAnchorKey,
102
+ sourceIdentityStatus: context.sourceIdentityStatus,
103
+ sourceIdentityAnchorKey: context.sourceIdentityAnchorKey,
104
+ targetIdentityAnchorKey: context.targetIdentityAnchorKey,
105
+ sourceIdentitySourcePath: context.sourceIdentitySourcePath,
106
+ targetIdentitySourcePath: context.targetIdentitySourcePath,
107
+ semanticIdentityHash: context.semanticIdentityHash,
108
+ sourceIdentityHash: context.sourceIdentityHash,
109
+ editContentHash: context.editContentHash,
96
110
  symbolName: context.symbolName,
97
111
  symbolKind: context.symbolKind,
98
112
  editKind: context.editKind,
@@ -105,6 +119,31 @@ function replayDiagnostic(code, context) {
105
119
  });
106
120
  }
107
121
 
122
+ function sourceIdentityDiagnosticContext(edit) {
123
+ const sourceIdentityAnchorKey = edit.sourceIdentityAnchorKey ?? edit.anchorKey;
124
+ const targetIdentityAnchorKey = edit.targetIdentityAnchorKey ?? edit.targetAnchorKey ?? sourceIdentityAnchorKey;
125
+ const sourceIdentitySourcePath = edit.sourceIdentitySourcePath ?? edit.originalSourcePath ?? edit.sourcePath;
126
+ const targetIdentitySourcePath = edit.targetIdentitySourcePath ?? edit.targetSourcePath ?? edit.sourcePath;
127
+ const moved = Boolean(
128
+ (sourceIdentityAnchorKey && targetIdentityAnchorKey && sourceIdentityAnchorKey !== targetIdentityAnchorKey)
129
+ || (sourceIdentitySourcePath && targetIdentitySourcePath && sourceIdentitySourcePath !== targetIdentitySourcePath)
130
+ );
131
+ return compactRecord({
132
+ originalSourcePath: edit.originalSourcePath,
133
+ targetSourcePath: edit.targetSourcePath,
134
+ anchorKey: edit.anchorKey,
135
+ targetAnchorKey: edit.targetAnchorKey,
136
+ sourceIdentityStatus: edit.sourceIdentityStatus ?? (moved ? 'moved-source' : 'same-source'),
137
+ sourceIdentityAnchorKey,
138
+ targetIdentityAnchorKey,
139
+ sourceIdentitySourcePath,
140
+ targetIdentitySourcePath,
141
+ semanticIdentityHash: edit.semanticIdentityHash,
142
+ sourceIdentityHash: edit.sourceIdentityHash,
143
+ editContentHash: edit.editContentHash
144
+ });
145
+ }
146
+
108
147
  function replayDiagnosticCategory(code, status) {
109
148
  if (code.includes('overlap')) return 'overlap';
110
149
  if (code.startsWith('missing-current-source') || code.startsWith('missing-head-source') || code.startsWith('missing-worker-source')) return 'missing-source';
@@ -0,0 +1,85 @@
1
+ export function explicitSourceReplacementReplayRange(edit, symbolRange, sourceText) {
2
+ if (edit.sourceRangeKind !== 'cross-language-explicit-source-replacement' || !symbolRange || typeof sourceText !== 'string') {
3
+ return undefined;
4
+ }
5
+ const deleted = uniqueTextRange(sourceText, symbolRange, edit.deletedText, 'deleted-text');
6
+ if (deleted.status === 'matched') return deleted;
7
+ const replacement = uniqueTextRange(sourceText, symbolRange, edit.replacementText, 'replacement-text');
8
+ if (replacement.status === 'matched') return replacement;
9
+ const relative = relativeAnchorRange(edit, symbolRange);
10
+ return {
11
+ ...relative,
12
+ conflictReasonCodes: [deleted.reasonCode, replacement.reasonCode].filter(Boolean)
13
+ };
14
+ }
15
+
16
+ function uniqueTextRange(sourceText, symbolRange, needle, label) {
17
+ if (typeof needle !== 'string' || needle.length === 0) {
18
+ return { status: 'missing', reasonCode: `current-symbol-explicit-source-replacement-${label}-missing` };
19
+ }
20
+ const symbolText = sourceText.slice(symbolRange.start, symbolRange.end);
21
+ const matches = [];
22
+ for (let index = symbolText.indexOf(needle); index >= 0; index = symbolText.indexOf(needle, index + 1)) {
23
+ const start = symbolRange.start + index;
24
+ if (isCodeOffset(sourceText, start)) matches.push(start);
25
+ }
26
+ if (matches.length !== 1) {
27
+ return { status: matches.length ? 'ambiguous' : 'missing', reasonCode: `current-symbol-explicit-source-replacement-${label}-${matches.length ? 'ambiguous' : 'missing'}` };
28
+ }
29
+ const first = matches[0] - symbolRange.start;
30
+ return {
31
+ status: 'matched',
32
+ range: { start: symbolRange.start + first, end: symbolRange.start + first + needle.length },
33
+ reasonCode: `current-symbol-explicit-source-replacement-${label}`
34
+ };
35
+ }
36
+
37
+ function isCodeOffset(sourceText, offset) {
38
+ let state = 'code';
39
+ for (let index = 0; index < offset; index += 1) {
40
+ const char = sourceText[index];
41
+ const next = sourceText[index + 1];
42
+ if (state === 'line-comment') {
43
+ if (char === '\n' || char === '\r') state = 'code';
44
+ continue;
45
+ }
46
+ if (state === 'block-comment') {
47
+ if (char === '*' && next === '/') {
48
+ index += 1;
49
+ state = 'code';
50
+ }
51
+ continue;
52
+ }
53
+ if (state === 'single' || state === 'double' || state === 'template') {
54
+ if (char === '\\') {
55
+ index += 1;
56
+ continue;
57
+ }
58
+ if ((state === 'single' && char === "'") || (state === 'double' && char === '"') || (state === 'template' && char === '`')) state = 'code';
59
+ continue;
60
+ }
61
+ if (char === '/' && next === '/') {
62
+ index += 1;
63
+ state = 'line-comment';
64
+ } else if (char === '#') {
65
+ state = 'line-comment';
66
+ } else if (char === '/' && next === '*') {
67
+ index += 1;
68
+ state = 'block-comment';
69
+ } else if (char === "'") state = 'single';
70
+ else if (char === '"') state = 'double';
71
+ else if (char === '`') state = 'template';
72
+ }
73
+ return state === 'code';
74
+ }
75
+
76
+ function relativeAnchorRange(edit, symbolRange) {
77
+ if (!Number.isFinite(edit.headAnchorStart) || !Number.isFinite(edit.headAnchorEnd)) return undefined;
78
+ if (!Number.isFinite(edit.headStart) || !Number.isFinite(edit.headEnd)) return undefined;
79
+ const offset = edit.headStart - edit.headAnchorStart;
80
+ const length = edit.headEnd - edit.headStart;
81
+ if (offset < 0 || length < 0) return undefined;
82
+ const range = { start: symbolRange.start + offset, end: symbolRange.start + offset + length };
83
+ if (range.start < symbolRange.start || range.end > symbolRange.end) return undefined;
84
+ return { range, reasonCode: 'current-symbol-explicit-source-replacement-relative-offset' };
85
+ }
@@ -226,6 +226,10 @@ function semanticEditOperationKind(region) {
226
226
  if (kind === 'import') return `${prefix}Import`;
227
227
  if (kind === 'type') return `${prefix}TypeDeclaration`;
228
228
  if (kind === 'property') return `${prefix}Property`;
229
+ if (kind === 'call') return `${prefix}Callsite`;
230
+ if (kind === 'controlFlow') return `${prefix}ControlFlow`;
231
+ if (kind === 'effect') return `${prefix}Effect`;
232
+ if (kind === 'mutation') return `${prefix}Mutation`;
229
233
  return `${prefix}Region`;
230
234
  }
231
235
 
@@ -1,5 +1,7 @@
1
1
  import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
2
 
3
+ const nestedBodyCoveredKinds = new Set(['export', 'call', 'controlFlow', 'effect', 'mutation']);
4
+
3
5
  export function projectionCoveredContainerOperationIds(operations, workerSourceText) {
4
6
  if (typeof workerSourceText !== 'string') return new Set();
5
7
  const result = new Set();
@@ -7,6 +9,9 @@ export function projectionCoveredContainerOperationIds(operations, workerSourceT
7
9
  if (!isProjectionCoverableContainer(operation)) continue;
8
10
  if (workerContainerCoveredByInsertedChildren(operation, operations, workerSourceText)) result.add(operation.id);
9
11
  }
12
+ for (const operation of operations ?? []) {
13
+ if (operationCoveredByBody(operation, operations, workerSourceText)) result.add(operation.id);
14
+ }
10
15
  return result;
11
16
  }
12
17
 
@@ -111,6 +116,28 @@ function workerContainerCoveredByInsertedChildren(container, operations, workerS
111
116
  return hashSemanticValue(stripped) === container.hashes.baseTextHash;
112
117
  }
113
118
 
119
+ function operationCoveredByBody(operation, operations, workerSourceText) {
120
+ const kind = operation.anchor?.regionKind;
121
+ if (!nestedBodyCoveredKinds.has(kind)) return false;
122
+ if (!['added', 'modified'].includes(operation.changeKind)) return false;
123
+ const range = spanOffsets(workerSourceText, operation.spans?.worker);
124
+ if (!range) return false;
125
+ return (operations ?? []).some((candidate) => (
126
+ candidate.id !== operation.id
127
+ && ['portable', 'already-applied'].includes(candidate.status)
128
+ && candidate.anchor?.regionKind === 'body'
129
+ && (kind !== 'export' || candidate.anchor?.symbolName === operation.anchor?.symbolName)
130
+ && sameOperationSourcePath(candidate, operation)
131
+ && containedRange(range, spanOffsets(workerSourceText, candidate.spans?.worker))
132
+ ));
133
+ }
134
+
135
+ function sameOperationSourcePath(left, right) {
136
+ const leftPath = left.anchor?.sourcePath ?? left.insertion?.sourcePath;
137
+ const rightPath = right.anchor?.sourcePath ?? right.insertion?.sourcePath;
138
+ return !leftPath || !rightPath || leftPath === rightPath;
139
+ }
140
+
114
141
  function containedRange(inner, outer) {
115
142
  return Boolean(inner && outer && outer.start <= inner.start && inner.end <= outer.end);
116
143
  }
@@ -168,6 +195,7 @@ function insertionAnchorResolution(sourceText, insertion, context) {
168
195
  const range = spanOffsets(sourceText, symbol?.sourceSpan);
169
196
  if (range) return { mode: candidate.mode, range };
170
197
  }
198
+ if (context.symbolIndexAvailable && candidates.some(hasSymbolAnchorIdentity)) return undefined;
171
199
  for (const candidate of candidates) {
172
200
  const range = spanOffsets(sourceText, candidate.headSpan);
173
201
  if (range) return { mode: candidate.mode, range };
@@ -175,6 +203,10 @@ function insertionAnchorResolution(sourceText, insertion, context) {
175
203
  return undefined;
176
204
  }
177
205
 
206
+ function hasSymbolAnchorIdentity(candidate) {
207
+ return Boolean(candidate.anchorKey || candidate.anchorSymbolId || candidate.anchorSymbolName);
208
+ }
209
+
178
210
  function insertionAnchorCandidates(insertion) {
179
211
  const seen = new Set();
180
212
  const result = [];
@@ -35,6 +35,7 @@ export function semanticIndexFromNativeDeclarations(declarations, input, options
35
35
  signatureHash: hashSemanticValue([input.language, declaration.nativeNode.kind, declaration.name, declaration.nativeNode.fields ?? {}]),
36
36
  definitionSpan: declaration.nativeNode.span,
37
37
  metadata: {
38
+ ...declaration.nativeNode.metadata,
38
39
  ownershipRegionId: ownershipRegion.id,
39
40
  ownershipRegionKey: ownershipRegion.key,
40
41
  ownershipRegionKind: ownershipRegion.regionKind