@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.
- package/README.md +11 -0
- package/dist/declarations/import-adapter-options-native.d.ts +3 -0
- package/dist/declarations/js-ts-safe-merge.d.ts +36 -0
- package/dist/declarations/native-project.d.ts +110 -0
- package/dist/internal/index-impl/createNativeProjectImportResult.js +177 -3
- package/dist/internal/index-impl/createTypeScriptCompilerNativeImporterAdapter.js +4 -0
- package/dist/internal/index-impl/replaySemanticEditProjection.js +1 -1
- package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +202 -16
- package/dist/internal/index-impl/typeScriptDeclaration.js +62 -9
- package/dist/internal/index-impl/visitTypeScriptAstNode.js +1 -0
- package/dist/js-ts-safe-merge-composed.js +6 -1
- package/dist/js-ts-safe-merge-semantic-artifact-ledger.js +197 -0
- package/dist/js-ts-safe-merge-semantic-artifacts.js +266 -0
- package/dist/js-ts-safe-merge.js +6 -1
- package/package.json +1 -1
|
@@ -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
|
-
...
|
|
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:
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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{
|
|
2
|
-
|
|
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
|
-
|
|
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 };
|