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

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.
@@ -6,24 +6,40 @@ export function semanticIndexFromNativeDeclarations(declarations, input, options
6
6
  const symbols = [];
7
7
  const occurrences = [];
8
8
  const relations = [];
9
- const facts = [];
9
+ const facts = input.sourceHash ? [fileHashFact(documentId, input, evidenceId)] : [];
10
10
  const mappings = [];
11
11
  for (const declaration of declarations) {
12
12
  const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${caseSensitiveIdFragment(declaration.name)}`;
13
+ const normalizedDeclaration = {
14
+ ...declaration,
15
+ symbolId,
16
+ regionKind: declarationRegionKind(declaration)
17
+ };
13
18
  const occurrenceId = `occ_${idFragment(declaration.nativeNode.id)}_${declaration.role ?? 'definition'}`;
14
19
  const ownershipRegion = semanticOwnershipRegionForDeclaration(input, {
15
- ...declaration,
20
+ ...normalizedDeclaration,
16
21
  nodeId: declaration.nativeNode.id,
17
22
  kind: declaration.nativeNode.kind,
18
23
  languageKind: declaration.nativeNode.languageKind,
19
24
  span: declaration.nativeNode.span,
20
25
  symbolId
21
26
  }, documentId);
27
+ const relationId = `rel_${idFragment(documentId)}_${idFragment(declaration.nativeNode.id)}`;
28
+ const moduleEdge = moduleEdgeForDeclaration(normalizedDeclaration, input, documentId, relationId, ownershipRegion);
29
+ const publicContractRegion = publicContractRegionForDeclaration(normalizedDeclaration, ownershipRegion, moduleEdge);
30
+ const reExportIdentity = reExportIdentityForDeclaration(normalizedDeclaration, input, documentId, relationId, ownershipRegion, moduleEdge);
31
+ const graphMetadata = {
32
+ ...(moduleEdge ? { moduleEdge } : {}),
33
+ ...(publicContractRegion ? { publicContract: true, publicContractRegionId: publicContractRegion.id } : {}),
34
+ ...(reExportIdentity ? { reExportIdentity } : {})
35
+ };
22
36
  declaration.nativeNode.metadata = {
23
37
  ...declaration.nativeNode.metadata,
38
+ ...declaration.metadata,
24
39
  ownershipRegionId: ownershipRegion.id,
25
40
  ownershipRegionKey: ownershipRegion.key,
26
- ownershipRegionKind: ownershipRegion.regionKind
41
+ ownershipRegionKind: ownershipRegion.regionKind,
42
+ ...graphMetadata
27
43
  };
28
44
  symbols.push({
29
45
  id: symbolId,
@@ -32,13 +48,14 @@ export function semanticIndexFromNativeDeclarations(declarations, input, options
32
48
  kind: declaration.symbolKind,
33
49
  language: input.language,
34
50
  nativeAstNodeId: declaration.nativeNode.id,
35
- signatureHash: hashSemanticValue([input.language, declaration.nativeNode.kind, declaration.name, declaration.nativeNode.fields ?? {}]),
51
+ signatureHash: declaration.signatureHash ?? hashSemanticValue([input.language, declaration.nativeNode.kind, declaration.name, declaration.nativeNode.fields ?? {}]),
36
52
  definitionSpan: declaration.nativeNode.span,
37
53
  metadata: {
38
54
  ...declaration.nativeNode.metadata,
39
55
  ownershipRegionId: ownershipRegion.id,
40
56
  ownershipRegionKey: ownershipRegion.key,
41
- ownershipRegionKind: ownershipRegion.regionKind
57
+ ownershipRegionKind: ownershipRegion.regionKind,
58
+ ...graphMetadata
42
59
  }
43
60
  });
44
61
  occurrences.push({
@@ -47,13 +64,16 @@ export function semanticIndexFromNativeDeclarations(declarations, input, options
47
64
  symbolId,
48
65
  role: declaration.role ?? 'definition',
49
66
  span: declaration.nativeNode.span,
50
- nativeAstNodeId: declaration.nativeNode.id
67
+ nativeAstNodeId: declaration.nativeNode.id,
68
+ ...(Object.keys(graphMetadata).length ? { metadata: graphMetadata } : {})
51
69
  });
52
70
  relations.push({
53
- id: `rel_${idFragment(documentId)}_${idFragment(declaration.nativeNode.id)}`,
71
+ id: relationId,
54
72
  sourceId: documentId,
55
73
  predicate: relationPredicateForDeclaration(declaration),
56
- targetId: symbolId
74
+ targetId: symbolId,
75
+ evidenceIds: [evidenceId],
76
+ ...(Object.keys(graphMetadata).length ? { metadata: graphMetadata } : {})
57
77
  });
58
78
  facts.push({
59
79
  id: `fact_${idFragment(declaration.nativeNode.id)}_kind`,
@@ -74,7 +94,7 @@ export function semanticIndexFromNativeDeclarations(declarations, input, options
74
94
  granularity: ownershipRegion.granularity,
75
95
  key: ownershipRegion.key
76
96
  }
77
- });
97
+ }, ...projectSymbolGraphFacts({ moduleEdge, publicContractRegion, reExportIdentity, relationId, symbolId, evidenceId }));
78
98
  mappings.push({
79
99
  id: `map_${idFragment(declaration.nativeNode.id)}`,
80
100
  nativeAstNodeId: declaration.nativeNode.id,
@@ -95,12 +115,18 @@ export function semanticIndexFromNativeDeclarations(declarations, input, options
95
115
  status: 'passed',
96
116
  path: input.sourcePath,
97
117
  summary: `Normalized ${options.astFormat ?? options.parser} native AST with ${declarations.length} declaration(s).`,
98
- metadata: {
99
- parser: options.parser,
100
- astFormat: options.astFormat,
101
- language: input.language,
102
- sourceHash: input.sourceHash
103
- }
118
+ metadata: {
119
+ parser: options.parser,
120
+ astFormat: options.astFormat,
121
+ language: input.language,
122
+ sourceHash: input.sourceHash,
123
+ graphRecords: {
124
+ fileHashes: input.sourceHash ? 1 : 0,
125
+ moduleEdges: facts.filter((fact) => fact.predicate === 'moduleEdge').length,
126
+ reExportIdentities: facts.filter((fact) => fact.predicate === 'reExportIdentity').length,
127
+ publicContractRegions: facts.filter((fact) => fact.predicate === 'publicContractRegion').length
128
+ }
129
+ }
104
130
  }];
105
131
  return {
106
132
  semanticIndex: createSemanticIndexRecord({
@@ -119,10 +145,170 @@ export function semanticIndexFromNativeDeclarations(declarations, input, options
119
145
  metadata: {
120
146
  parser: options.parser,
121
147
  astFormat: options.astFormat,
122
- coverage: 'native-ast-declarations'
148
+ coverage: 'native-ast-declarations',
149
+ graphCoverage: 'module-edge-declarations'
123
150
  }
124
151
  }),
125
152
  mappings,
126
153
  evidence
127
154
  };
128
155
  }
156
+
157
+ function fileHashFact(documentId, input, evidenceId) {
158
+ return {
159
+ id: `fact_${idFragment(documentId)}_file_hash`,
160
+ predicate: 'fileHash',
161
+ subjectId: documentId,
162
+ value: { ...hashParts(input.sourceHash), sourcePath: input.sourcePath, language: input.language },
163
+ evidenceIds: [evidenceId]
164
+ };
165
+ }
166
+
167
+ function declarationRegionKind(declaration) {
168
+ if (declaration.regionKind) return declaration.regionKind;
169
+ if (declaration.role === 'import' || declaration.importPath) return 'import';
170
+ if (declaration.role === 'export' || declaration.exported || declaration.reExport) return 'export';
171
+ if (declaration.publicContract || declaration.metadata?.publicContract) return 'export';
172
+ return undefined;
173
+ }
174
+
175
+ function moduleEdgeForDeclaration(declaration, input, documentId, relationId, ownershipRegion) {
176
+ const role = declaration.role;
177
+ if (role !== 'import' && role !== 'export') return undefined;
178
+ const moduleSpecifier = moduleSpecifierForDeclaration(declaration);
179
+ const edgeKind = role === 'import' ? 'import' : moduleSpecifier || declaration.reExport ? 're-export' : 'export';
180
+ return compactRecord({
181
+ kind: 'frontier.lang.moduleEdge',
182
+ version: 1,
183
+ id: relationId,
184
+ edgeKind,
185
+ role,
186
+ sourceDocumentId: documentId,
187
+ sourcePath: input.sourcePath,
188
+ sourceHash: input.sourceHash,
189
+ moduleSpecifier,
190
+ symbolId: declaration.symbolId,
191
+ relationId,
192
+ ownershipRegionId: ownershipRegion.id,
193
+ ownershipRegionKey: ownershipRegion.key,
194
+ importKind: declaration.importKind ?? declaration.metadata?.importKind,
195
+ exportKind: declaration.exportKind ?? declaration.metadata?.exportKind,
196
+ importedName: declaration.importedName ?? declaration.metadata?.importedName,
197
+ exportedName: declaration.exportedName ?? declaration.metadata?.exportedName,
198
+ localName: declaration.localName ?? declaration.metadata?.localName,
199
+ namespace: declaration.namespace ?? declaration.metadata?.namespace,
200
+ isTypeOnly: declaration.isTypeOnly ?? declaration.metadata?.isTypeOnly,
201
+ isReExport: edgeKind === 're-export',
202
+ publicContract: publicContractForDeclaration(declaration, edgeKind)
203
+ });
204
+ }
205
+
206
+ function moduleSpecifierForDeclaration(declaration) {
207
+ return firstString(
208
+ declaration.moduleSpecifier,
209
+ declaration.importPath,
210
+ declaration.exportPath,
211
+ declaration.source,
212
+ declaration.metadata?.moduleSpecifier,
213
+ declaration.metadata?.importPath,
214
+ declaration.metadata?.exportPath,
215
+ declaration.metadata?.source,
216
+ declaration.symbolKind === 'module' ? declaration.name : undefined
217
+ );
218
+ }
219
+
220
+ function publicContractForDeclaration(declaration, edgeKind) {
221
+ return Boolean(
222
+ declaration.publicContract
223
+ || declaration.exported
224
+ || declaration.metadata?.publicContract
225
+ || declaration.metadata?.publicContractRegion
226
+ || declaration.role === 'export'
227
+ || edgeKind === 'export'
228
+ || edgeKind === 're-export'
229
+ );
230
+ }
231
+
232
+ function publicContractRegionForDeclaration(declaration, ownershipRegion, moduleEdge) {
233
+ if (!publicContractForDeclaration(declaration, moduleEdge?.edgeKind)) return undefined;
234
+ return {
235
+ ...ownershipRegion,
236
+ regionKind: declaration.publicContractRegionKind ?? declaration.metadata?.publicContractRegionKind ?? ownershipRegion.regionKind,
237
+ publicContract: true,
238
+ exportedName: declaration.exportedName ?? declaration.metadata?.exportedName,
239
+ moduleSpecifier: moduleEdge?.moduleSpecifier,
240
+ edgeKind: moduleEdge?.edgeKind
241
+ };
242
+ }
243
+
244
+ function reExportIdentityForDeclaration(declaration, input, documentId, relationId, ownershipRegion, moduleEdge) {
245
+ if (!moduleEdge?.isReExport && !declaration.reExport) return undefined;
246
+ return compactRecord({
247
+ kind: 'frontier.lang.reExportIdentity',
248
+ version: 1,
249
+ id: `reexport_${idFragment(relationId)}`,
250
+ sourceDocumentId: documentId,
251
+ sourcePath: input.sourcePath,
252
+ sourceHash: input.sourceHash,
253
+ moduleSpecifier: moduleEdge?.moduleSpecifier,
254
+ exportedName: declaration.exportedName ?? declaration.metadata?.exportedName,
255
+ importedName: declaration.importedName ?? declaration.metadata?.importedName,
256
+ localName: declaration.localName ?? declaration.metadata?.localName,
257
+ namespace: declaration.namespace ?? declaration.metadata?.namespace,
258
+ isTypeOnly: declaration.isTypeOnly ?? declaration.metadata?.isTypeOnly,
259
+ symbolId: declaration.symbolId,
260
+ relationId,
261
+ ownershipRegionId: ownershipRegion.id,
262
+ ownershipRegionKey: ownershipRegion.key,
263
+ publicContract: true
264
+ });
265
+ }
266
+
267
+ function projectSymbolGraphFacts({ moduleEdge, publicContractRegion, reExportIdentity, relationId, symbolId, evidenceId }) {
268
+ return [
269
+ moduleEdge ? {
270
+ id: `fact_${idFragment(relationId)}_module_edge`,
271
+ predicate: 'moduleEdge',
272
+ subjectId: relationId,
273
+ objectId: symbolId,
274
+ value: moduleEdge,
275
+ evidenceIds: [evidenceId]
276
+ } : undefined,
277
+ reExportIdentity ? {
278
+ id: `fact_${idFragment(relationId)}_re_export_identity`,
279
+ predicate: 'reExportIdentity',
280
+ subjectId: symbolId,
281
+ objectId: relationId,
282
+ value: reExportIdentity,
283
+ evidenceIds: [evidenceId]
284
+ } : undefined,
285
+ publicContractRegion ? {
286
+ id: `fact_${idFragment(symbolId)}_public_contract_region`,
287
+ predicate: 'publicContractRegion',
288
+ subjectId: symbolId,
289
+ objectId: publicContractRegion.id,
290
+ value: publicContractRegion,
291
+ evidenceIds: [evidenceId]
292
+ } : undefined
293
+ ].filter(Boolean);
294
+ }
295
+
296
+ function hashParts(sourceHash) {
297
+ const text = String(sourceHash);
298
+ const separator = text.indexOf(':');
299
+ return {
300
+ sourceHash: text,
301
+ ...(separator > 0 ? { algorithm: text.slice(0, separator), value: text.slice(separator + 1) } : { value: text })
302
+ };
303
+ }
304
+
305
+ function compactRecord(record) {
306
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
307
+ }
308
+
309
+ function firstString(...values) {
310
+ for (const value of values) {
311
+ if (value !== undefined && value !== null && String(value)) return String(value);
312
+ }
313
+ return undefined;
314
+ }
@@ -1,14 +1,67 @@
1
- import{declarationRecord}from'./declarationRecord.js';import{namedDeclaration}from'./namedDeclaration.js';import{nativeNodeId}from'./nativeNodeId.js';import{stringFromTsExpression}from'./stringFromTsExpression.js';
2
- export function typeScriptDeclaration(node, kind, nativeNodeId, input) {
1
+ import{hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';import{idFragment}from'../../native-import-utils.js';
2
+ import{declarationRecord}from'./declarationRecord.js';import{identifierName}from'./identifierName.js';import{namedDeclaration}from'./namedDeclaration.js';import{stringFromTsExpression}from'./stringFromTsExpression.js';
3
+ export function typeScriptDeclaration(node, kind, nativeNodeId, input, options = {}) {
4
+ const enrich = (declaration, symbolNode = node.name ?? node) => enrichTypeScriptDeclaration(node, symbolNode, declaration, input, options);
3
5
  if (kind === 'ImportDeclaration' || kind === 'ImportEqualsDeclaration') {
4
6
  const name = stringFromTsExpression(node.moduleSpecifier) ?? stringFromTsExpression(node.externalModuleReference?.expression);
5
- if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
7
+ if (name) return enrich(declarationRecord(input, nativeNodeId, name, 'module', 'import'), node.moduleSpecifier ?? node.externalModuleReference?.expression ?? node);
6
8
  }
7
- if (kind === 'FunctionDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'function');
8
- if (kind === 'ClassDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'class');
9
- if (kind === 'InterfaceDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'interface');
10
- if (kind === 'TypeAliasDeclaration' || kind === 'EnumDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'type');
11
- if (kind === 'VariableDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'variable');
12
- if (kind === 'MethodDeclaration' || kind === 'MethodSignature') return namedDeclaration(input, nativeNodeId, node.name, 'method');
9
+ if (kind === 'FunctionDeclaration') return enrich(namedDeclaration(input, nativeNodeId, node.name, 'function'));
10
+ if (kind === 'ClassDeclaration') return enrich(namedDeclaration(input, nativeNodeId, node.name, 'class'));
11
+ if (kind === 'InterfaceDeclaration') return enrich(namedDeclaration(input, nativeNodeId, node.name, 'interface'));
12
+ if (kind === 'TypeAliasDeclaration' || kind === 'EnumDeclaration') return enrich(namedDeclaration(input, nativeNodeId, node.name, 'type'));
13
+ if (kind === 'VariableDeclaration') return enrich(namedDeclaration(input, nativeNodeId, node.name, 'variable'));
14
+ if (kind === 'MethodDeclaration' || kind === 'MethodSignature') return enrich(namedDeclaration(input, nativeNodeId, node.name, 'method'));
13
15
  return undefined;
14
16
  }
17
+
18
+ function enrichTypeScriptDeclaration(node, symbolNode, declaration, input, options) {
19
+ if (!declaration) return declaration;
20
+ const checker = options.typeChecker ?? options.checker ?? options.program?.getTypeChecker?.();
21
+ const symbol = safeCall(checker?.getSymbolAtLocation, checker, symbolNode) ?? safeCall(checker?.getSymbolAtLocation, checker, node.name);
22
+ if (!symbol) return declaration;
23
+ const aliasedSymbol = safeCall(checker?.getAliasedSymbol, checker, symbol);
24
+ const targetSymbol = aliasedSymbol && aliasedSymbol !== symbol ? aliasedSymbol : undefined;
25
+ const identitySymbol = targetSymbol ?? symbol;
26
+ const fullyQualifiedName = stringValue(safeCall(checker?.getFullyQualifiedName, checker, identitySymbol));
27
+ const localName = stringValue(symbol.escapedName ?? symbol.name) ?? declaration.name;
28
+ const targetName = targetSymbol ? stringValue(targetSymbol.escapedName ?? targetSymbol.name) : undefined;
29
+ const identity = fullyQualifiedName ?? targetName ?? localName;
30
+ const compilerSymbol = compactRecord({
31
+ parser: options.parser,
32
+ localName,
33
+ targetName,
34
+ fullyQualifiedName,
35
+ flags: numberValue(symbol.flags),
36
+ targetFlags: numberValue(targetSymbol?.flags),
37
+ declarations: Array.isArray(identitySymbol.declarations) ? identitySymbol.declarations.length : undefined,
38
+ aliased: Boolean(targetSymbol)
39
+ });
40
+ return {
41
+ ...declaration,
42
+ symbolId: `symbol:${input.language}:compiler:${declaration.role === 'import' ? 'import:' : ''}${idFragment(identity)}`,
43
+ signatureHash: hashSemanticValue([input.language, declaration.symbolKind, identity, compilerSymbol]),
44
+ metadata: {
45
+ ...declaration.metadata,
46
+ compilerSymbol,
47
+ compilerSymbolIdentityHash: hashSemanticValue(compilerSymbol)
48
+ }
49
+ };
50
+ }
51
+
52
+ function safeCall(fn, receiver, ...args) {
53
+ if (typeof fn !== 'function') return undefined;
54
+ try { return fn.apply(receiver, args); } catch { return undefined; }
55
+ }
56
+
57
+ function stringValue(value) {
58
+ return value === undefined || value === null || value === '' ? undefined : String(value);
59
+ }
60
+
61
+ function numberValue(value) {
62
+ return Number.isFinite(value) ? value : undefined;
63
+ }
64
+
65
+ function compactRecord(record) {
66
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
67
+ }
@@ -33,6 +33,7 @@ export function visitTypeScriptAstNode(node, sourceFile, context, propertyPath,
33
33
  fields: primitiveTypeScriptFields(node, kind),
34
34
  children,
35
35
  metadata: {
36
+ ...declaration?.metadata,
36
37
  astFormat: context.options.astFormat,
37
38
  propertyPath,
38
39
  pos: numberOrUndefined(node.pos),
@@ -5,6 +5,7 @@ import {
5
5
  jsTsSafeMergeGateOrder
6
6
  } from './js-ts-safe-merge-constants.js';
7
7
  import { safeMergeJsTsImportsAndDeclarations } from './js-ts-safe-merge.js';
8
+ import { createJsTsSafeMergeSemanticArtifacts } from './js-ts-safe-merge-semantic-artifacts.js';
8
9
  import {
9
10
  applyJsTsPreparedMemberAdditions,
10
11
  neutralizeJsTsSafeMemberMergeSources
@@ -40,7 +41,7 @@ function safeMergeJsTsSource(input = {}) {
40
41
  const memberAdditions = memberNeutralization.analysis.preparedRegions.reduce((total, region) => (
41
42
  total + region.workerAddedKeys.length + region.headAddedKeys.length
42
43
  ), 0);
43
- return {
44
+ const result = {
44
45
  ...topLevelResult,
45
46
  id: String(input.id ?? topLevelResult.id),
46
47
  mergedSourceText: memberApplication.sourceText,
@@ -65,6 +66,10 @@ function safeMergeJsTsSource(input = {}) {
65
66
  }
66
67
  }
67
68
  };
69
+ return {
70
+ ...result,
71
+ semanticArtifacts: createJsTsSafeMergeSemanticArtifacts(input, result)
72
+ };
68
73
  }
69
74
 
70
75
  function hasMemberMergePolicy(input) {
@@ -0,0 +1,197 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { idFragment } from './native-import-utils.js';
3
+ import { detectLineEnding, normalizeLineEndings } from './js-ts-safe-merge-context.js';
4
+
5
+ function createOperationsFromLedgers(context) {
6
+ const headByKey = new Map(context.head.entries.map((entry) => [entry.key, entry]));
7
+ return context.merged.entries.flatMap((entry, index) => {
8
+ const headEntry = headByKey.get(entry.key);
9
+ if (headEntry) {
10
+ if (sameEntryText(headEntry.text, entry.text)) return [];
11
+ return [operationRecord({
12
+ ...context,
13
+ index,
14
+ kind: entry.kind === 'import' ? 'jsTsReplaceImport' : 'jsTsReplaceDeclaration',
15
+ changeKind: 'modified',
16
+ entry,
17
+ headEntry,
18
+ insertion: undefined,
19
+ currentText: headEntry.text,
20
+ replacementText: entry.text
21
+ })];
22
+ }
23
+ return [operationRecord({
24
+ ...context,
25
+ index,
26
+ kind: entry.kind === 'import' ? 'jsTsInsertImport' : 'jsTsInsertDeclaration',
27
+ changeKind: 'added',
28
+ entry,
29
+ headEntry: undefined,
30
+ insertion: insertionAnchorForMergedEntry(entry, index, context),
31
+ currentText: '',
32
+ replacementText: insertionText(entry.text, context.head.sourceText)
33
+ })];
34
+ });
35
+ }
36
+
37
+ function operationRecord(input) {
38
+ const anchor = entryAnchor(input.entry, input.sourcePath, input.language);
39
+ const operation = {
40
+ id: `js_ts_safe_merge_op_${idFragment([input.id, input.index, input.entry.key].join(':'))}`,
41
+ kind: input.kind,
42
+ changeKind: input.changeKind,
43
+ anchor,
44
+ insertion: input.insertion,
45
+ spans: {
46
+ head: input.headEntry ? spanForEntry(input.headEntry) : undefined,
47
+ worker: spanForEntry(input.entry)
48
+ },
49
+ hashes: {
50
+ baseSourceHash: input.input.baseHash,
51
+ workerSourceHash: input.input.workerHash,
52
+ headSourceHash: input.input.headHash,
53
+ headTextHash: hashSemanticValue(input.currentText),
54
+ workerTextHash: hashSemanticValue(input.replacementText)
55
+ },
56
+ status: 'portable',
57
+ readiness: 'ready',
58
+ confidence: 1,
59
+ reasonCodes: ['js-ts-safe-merge-gates-passed', 'js-ts-ledger-source-edit'],
60
+ evidenceIds: [`evidence_${idFragment(input.id)}_js_ts_safe_merge_semantic_replay`],
61
+ metadata: {
62
+ autoMergeClaim: false,
63
+ semanticEquivalenceClaim: false,
64
+ ledgerKey: input.entry.key
65
+ }
66
+ };
67
+ return { ...operation, operationContentHash: hashSemanticValue(operation) };
68
+ }
69
+
70
+ function sourceEditForOperation(operation, order, headSourceText, mergedSourceText) {
71
+ const headStart = operation.spans.head?.start ?? insertionOffsetFromAnchor(operation.insertion, headSourceText);
72
+ const headEnd = operation.spans.head?.end ?? headStart;
73
+ const workerStart = operation.spans.worker?.start ?? 0;
74
+ const workerEnd = operation.spans.worker?.end ?? workerStart;
75
+ const current = headSourceText.slice(headStart, headEnd);
76
+ const replacementSpanText = mergedSourceText.slice(workerStart, workerEnd);
77
+ const replacement = operation.changeKind === 'added'
78
+ ? insertionText(replacementSpanText, headSourceText)
79
+ : replacementSpanText;
80
+ return {
81
+ operationId: operation.id,
82
+ order,
83
+ kind: operation.kind,
84
+ editKind: operation.changeKind === 'added' ? 'insert' : 'replace',
85
+ changeKind: operation.changeKind,
86
+ anchorKey: operation.anchor.key,
87
+ conflictKey: operation.anchor.conflictKey,
88
+ regionId: operation.anchor.regionId,
89
+ regionKind: operation.anchor.regionKind,
90
+ sourcePath: operation.anchor.sourcePath,
91
+ symbolId: operation.anchor.symbolId,
92
+ symbolName: operation.anchor.symbolName,
93
+ symbolKind: operation.anchor.symbolKind,
94
+ operationContentHash: operation.operationContentHash,
95
+ insertion: operation.insertion,
96
+ start: headStart,
97
+ end: headEnd,
98
+ workerStart,
99
+ workerEnd,
100
+ current,
101
+ replacement,
102
+ replacementSpanText
103
+ };
104
+ }
105
+
106
+ function insertionAnchorForMergedEntry(entry, index, context) {
107
+ const previous = nearestHeadEntry(context.merged.entries, context.head.entries, index, -1);
108
+ if (previous) return {
109
+ mode: 'after',
110
+ anchorKey: previous.key,
111
+ anchorSymbolName: entryName(previous),
112
+ anchorSymbolKind: entrySymbolKind(previous),
113
+ headSpan: spanForEntry(previous),
114
+ sourcePath: context.sourcePath
115
+ };
116
+ const next = nearestHeadEntry(context.merged.entries, context.head.entries, index, 1);
117
+ if (next) return {
118
+ mode: 'before',
119
+ anchorKey: next.key,
120
+ anchorSymbolName: entryName(next),
121
+ anchorSymbolKind: entrySymbolKind(next),
122
+ headSpan: spanForEntry(next),
123
+ sourcePath: context.sourcePath
124
+ };
125
+ return { mode: 'file-end', sourcePath: context.sourcePath };
126
+ }
127
+
128
+ function nearestHeadEntry(mergedEntries, headEntries, startIndex, step) {
129
+ const headByKey = new Map(headEntries.map((entry) => [entry.key, entry]));
130
+ for (let index = startIndex + step; index >= 0 && index < mergedEntries.length; index += step) {
131
+ const headEntry = headByKey.get(mergedEntries[index].key);
132
+ if (headEntry) return headEntry;
133
+ }
134
+ return undefined;
135
+ }
136
+
137
+ function insertionOffsetFromAnchor(insertion, sourceText) {
138
+ if (insertion?.mode === 'file-start') return 0;
139
+ if (insertion?.mode === 'file-end') return sourceText.length;
140
+ const span = insertion?.headSpan;
141
+ if (!span) return sourceText.length;
142
+ if (insertion.mode === 'before') return lineStartOffset(sourceText, span.start);
143
+ return lineEndOffset(sourceText, span.end);
144
+ }
145
+
146
+ function entryAnchor(entry, sourcePath, language) {
147
+ const name = entryName(entry);
148
+ const key = `source#${sourcePath ?? 'unknown'}#${entry.kind}#${name ?? entry.key}`;
149
+ return {
150
+ key,
151
+ conflictKey: key,
152
+ regionId: key,
153
+ regionKind: entry.kind === 'import' ? 'import' : 'declaration',
154
+ granularity: 'js-ts-ledger-entry',
155
+ language,
156
+ sourcePath,
157
+ symbolId: key,
158
+ symbolName: name,
159
+ symbolKind: entrySymbolKind(entry),
160
+ sourceSpan: spanForEntry(entry)
161
+ };
162
+ }
163
+
164
+ function entryName(entry) {
165
+ if (entry.kind === 'import') return entry.importInfo?.moduleSpecifier;
166
+ return entry.names?.join('|') ?? entry.key;
167
+ }
168
+
169
+ function entrySymbolKind(entry) {
170
+ if (entry.kind === 'import') return 'import';
171
+ return entry.declarationInfo?.declarationKind ?? entry.kind;
172
+ }
173
+
174
+ function spanForEntry(entry) {
175
+ return { start: entry.start, end: entry.end };
176
+ }
177
+
178
+ function insertionText(text, sourceText) {
179
+ const lineEnding = detectLineEnding(sourceText);
180
+ const normalized = normalizeLineEndings(String(text ?? '').trimEnd(), lineEnding);
181
+ return normalized.endsWith(lineEnding) ? normalized : `${normalized}${lineEnding}`;
182
+ }
183
+
184
+ function sameEntryText(left, right) {
185
+ return normalizeLineEndings(String(left ?? '').trim(), '\n') === normalizeLineEndings(String(right ?? '').trim(), '\n');
186
+ }
187
+
188
+ function lineStartOffset(sourceText, offset) {
189
+ return sourceText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1;
190
+ }
191
+
192
+ function lineEndOffset(sourceText, offset) {
193
+ const lineEnd = sourceText.indexOf('\n', offset);
194
+ return lineEnd === -1 ? sourceText.length : lineEnd + 1;
195
+ }
196
+
197
+ export { createOperationsFromLedgers, sourceEditForOperation };