@shapeshift-labs/frontier-lang-compiler 0.2.21 → 0.2.23
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 +47 -0
- package/bench/smoke.mjs +51 -2
- package/dist/index.d.ts +106 -0
- package/dist/index.js +1459 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -76,6 +76,13 @@ const semanticMergeReadinessRank = Object.freeze({
|
|
|
76
76
|
blocked: 3
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
const nativeFeatureEvidenceRiskRank = Object.freeze({
|
|
80
|
+
low: 0,
|
|
81
|
+
medium: 1,
|
|
82
|
+
high: 2,
|
|
83
|
+
critical: 3
|
|
84
|
+
});
|
|
85
|
+
|
|
79
86
|
export const NativeImportRoundtripReadinessStatuses = Object.freeze([
|
|
80
87
|
'exact',
|
|
81
88
|
'preserved-source',
|
|
@@ -136,6 +143,117 @@ export const NativeImportReadinessBySeverity = Object.freeze({
|
|
|
136
143
|
error: 'blocked'
|
|
137
144
|
});
|
|
138
145
|
|
|
146
|
+
export const NativeImportFeatureEvidencePolicies = Object.freeze({
|
|
147
|
+
macroExpansion: nativeImportFeatureEvidencePolicy('macroExpansion', {
|
|
148
|
+
category: 'macroExpansion',
|
|
149
|
+
risk: 'high',
|
|
150
|
+
minimumReadiness: 'needs-review',
|
|
151
|
+
requiredEvidenceKeys: ['macroDefinitionsHash', 'expandedSourceHash'],
|
|
152
|
+
recommendedEvidenceKeys: ['expansionMapId', 'sourceMapId', 'macroCallSites'],
|
|
153
|
+
notes: ['Macro-expanded code must retain a link from generated output back to macro call sites before semantic merges can be trusted.']
|
|
154
|
+
}),
|
|
155
|
+
macroHygiene: nativeImportFeatureEvidencePolicy('macroHygiene', {
|
|
156
|
+
category: 'macroExpansion',
|
|
157
|
+
risk: 'critical',
|
|
158
|
+
minimumReadiness: 'needs-review',
|
|
159
|
+
missingEvidenceReadiness: 'blocked',
|
|
160
|
+
requiredEvidenceKeys: ['hygieneContextHash', 'bindingMapId'],
|
|
161
|
+
recommendedEvidenceKeys: ['expansionMapId', 'captureSetHash'],
|
|
162
|
+
notes: ['Hygiene-sensitive macros can change binding identity even when emitted text looks equivalent.']
|
|
163
|
+
}),
|
|
164
|
+
preprocessor: nativeImportFeatureEvidencePolicy('preprocessor', {
|
|
165
|
+
category: 'preprocessor',
|
|
166
|
+
risk: 'high',
|
|
167
|
+
minimumReadiness: 'needs-review',
|
|
168
|
+
requiredEvidenceKeys: ['preprocessedOutputHash', 'definesHash'],
|
|
169
|
+
recommendedEvidenceKeys: ['includeGraphHash', 'conditionalBranches', 'sourceMapId'],
|
|
170
|
+
notes: ['Preprocessor imports need the active defines/includes and preprocessed output hash to make replayable claims.']
|
|
171
|
+
}),
|
|
172
|
+
conditionalCompilation: nativeImportFeatureEvidencePolicy('conditionalCompilation', {
|
|
173
|
+
category: 'conditionalCompilation',
|
|
174
|
+
risk: 'high',
|
|
175
|
+
minimumReadiness: 'needs-review',
|
|
176
|
+
requiredEvidenceKeys: ['activeBranches', 'inactiveBranchesHash'],
|
|
177
|
+
recommendedEvidenceKeys: ['compileTarget', 'featureFlags', 'preprocessedOutputHash'],
|
|
178
|
+
notes: ['Conditional branches that were not active still affect portability and conflict review.']
|
|
179
|
+
}),
|
|
180
|
+
metaprogramming: nativeImportFeatureEvidencePolicy('metaprogramming', {
|
|
181
|
+
category: 'metaprogramming',
|
|
182
|
+
risk: 'critical',
|
|
183
|
+
minimumReadiness: 'needs-review',
|
|
184
|
+
missingEvidenceReadiness: 'blocked',
|
|
185
|
+
requiredEvidenceKeys: ['generatedArtifactHash', 'generatorIdentity'],
|
|
186
|
+
recommendedEvidenceKeys: ['generatorInputsHash', 'generatedRanges', 'replayCommand'],
|
|
187
|
+
notes: ['Generated or metaprogrammed declarations need replayable generator identity and input evidence.']
|
|
188
|
+
}),
|
|
189
|
+
reflection: nativeImportFeatureEvidencePolicy('reflection', {
|
|
190
|
+
category: 'reflection',
|
|
191
|
+
risk: 'high',
|
|
192
|
+
minimumReadiness: 'needs-review',
|
|
193
|
+
requiredEvidenceKeys: ['reflectionSurface', 'runtimeContract'],
|
|
194
|
+
recommendedEvidenceKeys: ['observedMembers', 'fixtureIds', 'runtimeVersion'],
|
|
195
|
+
notes: ['Reflection-heavy code needs a declared runtime contract because static AST evidence is incomplete.']
|
|
196
|
+
}),
|
|
197
|
+
dynamicRuntime: nativeImportFeatureEvidencePolicy('dynamicRuntime', {
|
|
198
|
+
category: 'reflection',
|
|
199
|
+
risk: 'high',
|
|
200
|
+
minimumReadiness: 'needs-review',
|
|
201
|
+
requiredEvidenceKeys: ['runtimeContract'],
|
|
202
|
+
recommendedEvidenceKeys: ['fixtureIds', 'observedEffects', 'runtimeVersion'],
|
|
203
|
+
notes: ['Dynamic runtime behavior should stay review-required until fixtures or traces describe the observed contract.']
|
|
204
|
+
}),
|
|
205
|
+
dynamicDispatch: nativeImportFeatureEvidencePolicy('dynamicDispatch', {
|
|
206
|
+
category: 'overloadTypeInference',
|
|
207
|
+
risk: 'medium',
|
|
208
|
+
minimumReadiness: 'needs-review',
|
|
209
|
+
requiredEvidenceKeys: ['dispatchTargets'],
|
|
210
|
+
recommendedEvidenceKeys: ['callGraphId', 'typeEvidenceId', 'fixtureIds'],
|
|
211
|
+
notes: ['Dynamic dispatch needs candidate target evidence before call graph or porting claims are merge-ready.']
|
|
212
|
+
}),
|
|
213
|
+
generatedCode: nativeImportFeatureEvidencePolicy('generatedCode', {
|
|
214
|
+
category: 'generatedCode',
|
|
215
|
+
risk: 'high',
|
|
216
|
+
minimumReadiness: 'needs-review',
|
|
217
|
+
requiredEvidenceKeys: ['generatedArtifactHash', 'generatedRanges'],
|
|
218
|
+
recommendedEvidenceKeys: ['generatorIdentity', 'generatorInputsHash', 'sourceMapId'],
|
|
219
|
+
notes: ['Generated code must preserve generated ranges and artifact hashes so workers can avoid editing derived output blindly.']
|
|
220
|
+
}),
|
|
221
|
+
overloadResolution: nativeImportFeatureEvidencePolicy('overloadResolution', {
|
|
222
|
+
category: 'overloadTypeInference',
|
|
223
|
+
risk: 'medium',
|
|
224
|
+
minimumReadiness: 'needs-review',
|
|
225
|
+
requiredEvidenceKeys: ['resolvedOverloads'],
|
|
226
|
+
recommendedEvidenceKeys: ['typeEvidenceId', 'compilerVersion', 'callSiteSpans'],
|
|
227
|
+
notes: ['Overload-sensitive imports should record compiler/type evidence for each call site.']
|
|
228
|
+
}),
|
|
229
|
+
typeInference: nativeImportFeatureEvidencePolicy('typeInference', {
|
|
230
|
+
category: 'overloadTypeInference',
|
|
231
|
+
risk: 'medium',
|
|
232
|
+
minimumReadiness: 'needs-review',
|
|
233
|
+
requiredEvidenceKeys: ['inferredTypesHash'],
|
|
234
|
+
recommendedEvidenceKeys: ['typeEvidenceId', 'compilerVersion', 'symbolTableHash'],
|
|
235
|
+
notes: ['Inferred types need a stable type-evidence hash before cross-language projection can claim fidelity.']
|
|
236
|
+
}),
|
|
237
|
+
unsupportedSyntax: nativeImportFeatureEvidencePolicy('unsupportedSyntax', {
|
|
238
|
+
category: 'unsupportedSyntax',
|
|
239
|
+
risk: 'high',
|
|
240
|
+
minimumReadiness: 'needs-review',
|
|
241
|
+
missingEvidenceReadiness: 'blocked',
|
|
242
|
+
requiredEvidenceKeys: ['unsupportedSyntaxKind', 'sourceSpan'],
|
|
243
|
+
recommendedEvidenceKeys: ['parserDiagnosticId', 'nativeAstNodeId', 'sourceSnippetHash'],
|
|
244
|
+
notes: ['Unsupported syntax must remain anchored to source spans and parser diagnostics for later adapter work.']
|
|
245
|
+
}),
|
|
246
|
+
unsupportedSemantic: nativeImportFeatureEvidencePolicy('unsupportedSemantic', {
|
|
247
|
+
category: 'unsupportedSyntax',
|
|
248
|
+
risk: 'high',
|
|
249
|
+
minimumReadiness: 'needs-review',
|
|
250
|
+
missingEvidenceReadiness: 'blocked',
|
|
251
|
+
requiredEvidenceKeys: ['unsupportedSemanticKind', 'semanticSymbolId'],
|
|
252
|
+
recommendedEvidenceKeys: ['semanticIndexId', 'sourceMapId', 'reason'],
|
|
253
|
+
notes: ['Unsupported semantics should name the affected symbol so merge tools can isolate the unsafe region.']
|
|
254
|
+
})
|
|
255
|
+
});
|
|
256
|
+
|
|
139
257
|
export const NativeImportRegionTaxonomyKinds = Object.freeze([
|
|
140
258
|
'symbol',
|
|
141
259
|
'declaration',
|
|
@@ -211,6 +329,14 @@ export const NativeImportLanguageProfiles = Object.freeze([
|
|
|
211
329
|
nativeImportLanguageProfile('r', { aliases: ['R'], extensions: ['.r', '.R'], parserAdapters: ['r-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] })
|
|
212
330
|
]);
|
|
213
331
|
|
|
332
|
+
export const ExternalSemanticIndexFormats = Object.freeze([
|
|
333
|
+
'frontier-semantic-index',
|
|
334
|
+
'scip',
|
|
335
|
+
'lsif',
|
|
336
|
+
'lsp',
|
|
337
|
+
'semanticdb'
|
|
338
|
+
]);
|
|
339
|
+
|
|
214
340
|
export function normalizeCompileTarget(target) {
|
|
215
341
|
const normalized = String(target ?? 'typescript').toLowerCase();
|
|
216
342
|
const canonical = canonicalTargets[normalized] ?? normalized;
|
|
@@ -426,6 +552,1216 @@ export function compileNativeSource(input, options = {}) {
|
|
|
426
552
|
};
|
|
427
553
|
}
|
|
428
554
|
|
|
555
|
+
export function importExternalSemanticIndex(input) {
|
|
556
|
+
const payload = input?.payload ?? input?.semanticIndex ?? input;
|
|
557
|
+
if (!payload || typeof payload !== 'object') {
|
|
558
|
+
throw new Error('importExternalSemanticIndex requires a payload object');
|
|
559
|
+
}
|
|
560
|
+
const format = normalizeExternalSemanticIndexFormat(input?.format ?? inferExternalSemanticIndexFormat(payload));
|
|
561
|
+
const idPart = idFragment(input?.id ?? input?.sourcePath ?? input?.projectRoot ?? format);
|
|
562
|
+
const context = {
|
|
563
|
+
format,
|
|
564
|
+
idPart,
|
|
565
|
+
language: normalizeExternalSemanticLanguage(input?.language ?? payload.language ?? payload.languageId),
|
|
566
|
+
sourcePath: input?.sourcePath ?? payload.sourcePath ?? payload.uri ?? payload.path,
|
|
567
|
+
sourceHash: input?.sourceHash ?? payload.sourceHash ?? payload.md5,
|
|
568
|
+
projectRoot: input?.projectRoot ?? payload.projectRoot ?? payload.project_root ?? payload.metadata?.projectRoot ?? payload.metadata?.project_root,
|
|
569
|
+
parser: input?.parser ?? `${format}.external-semantic-index`,
|
|
570
|
+
metadata: input?.metadata ?? {}
|
|
571
|
+
};
|
|
572
|
+
const normalized = normalizeExternalSemanticIndexPayload(payload, context);
|
|
573
|
+
const evidence = attachNativeImportLossSummary(
|
|
574
|
+
uniqueByEvidenceId([...(normalized.evidence ?? []), ...(input?.evidence ?? [])]),
|
|
575
|
+
summarizeNativeImportLosses(normalized.losses ?? [], {
|
|
576
|
+
evidence: [...(normalized.evidence ?? []), ...(input?.evidence ?? [])],
|
|
577
|
+
parser: context.parser,
|
|
578
|
+
scanKind: 'external-semantic-index',
|
|
579
|
+
semanticStatus: normalized.semanticStatus
|
|
580
|
+
})
|
|
581
|
+
);
|
|
582
|
+
const losses = normalizeNativeLossRecords(normalized.losses ?? []);
|
|
583
|
+
const semanticIndex = createSemanticIndexRecord({
|
|
584
|
+
id: input?.semanticIndexId ?? normalized.semanticIndexId ?? `index_${idPart}_${idFragment(format)}`,
|
|
585
|
+
repository: normalized.repository,
|
|
586
|
+
documents: normalized.documents,
|
|
587
|
+
symbols: normalized.symbols,
|
|
588
|
+
occurrences: normalized.occurrences,
|
|
589
|
+
relations: normalized.relations,
|
|
590
|
+
facts: normalized.facts,
|
|
591
|
+
evidence,
|
|
592
|
+
metadata: {
|
|
593
|
+
format,
|
|
594
|
+
parser: context.parser,
|
|
595
|
+
source: 'external-semantic-index',
|
|
596
|
+
projectRoot: context.projectRoot,
|
|
597
|
+
semanticStatus: normalized.semanticStatus,
|
|
598
|
+
...normalized.metadata,
|
|
599
|
+
...context.metadata
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
const sourceMapMappings = externalSemanticSourceMapMappings(semanticIndex, {
|
|
603
|
+
evidence,
|
|
604
|
+
losses,
|
|
605
|
+
sourcePath: context.sourcePath,
|
|
606
|
+
sourceHash: context.sourceHash
|
|
607
|
+
});
|
|
608
|
+
const sourceMaps = sourceMapMappings.length
|
|
609
|
+
? [createSourceMapRecord({
|
|
610
|
+
id: input?.sourceMapId ?? `source_map_${idPart}_${idFragment(format)}`,
|
|
611
|
+
sourcePath: context.sourcePath,
|
|
612
|
+
sourceHash: context.sourceHash,
|
|
613
|
+
semanticIndexId: semanticIndex.id,
|
|
614
|
+
mappings: sourceMapMappings,
|
|
615
|
+
evidence,
|
|
616
|
+
metadata: {
|
|
617
|
+
format,
|
|
618
|
+
source: 'external-semantic-index',
|
|
619
|
+
projectRoot: context.projectRoot
|
|
620
|
+
}
|
|
621
|
+
})]
|
|
622
|
+
: [];
|
|
623
|
+
const document = createDocument({
|
|
624
|
+
id: input?.documentId ?? `document_${idPart}_${idFragment(format)}`,
|
|
625
|
+
name: input?.documentName ?? context.sourcePath ?? `${format} semantic index`,
|
|
626
|
+
nodes: [],
|
|
627
|
+
rootIds: [],
|
|
628
|
+
metadata: {
|
|
629
|
+
sourceLanguage: context.language,
|
|
630
|
+
sourcePath: context.sourcePath,
|
|
631
|
+
sourceHash: context.sourceHash,
|
|
632
|
+
semanticStatus: normalized.semanticStatus,
|
|
633
|
+
externalSemanticIndexFormat: format
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
const universalAst = createUniversalAstEnvelope({
|
|
637
|
+
id: input?.universalAstId ?? `universal_ast_${idPart}_${idFragment(format)}`,
|
|
638
|
+
document,
|
|
639
|
+
nativeSources: [],
|
|
640
|
+
semanticIndex,
|
|
641
|
+
sourceMaps,
|
|
642
|
+
losses,
|
|
643
|
+
evidence,
|
|
644
|
+
metadata: {
|
|
645
|
+
sourceLanguage: context.language,
|
|
646
|
+
sourcePath: context.sourcePath,
|
|
647
|
+
sourceHash: context.sourceHash,
|
|
648
|
+
projectRoot: context.projectRoot,
|
|
649
|
+
externalSemanticIndexFormat: format,
|
|
650
|
+
semanticStatus: normalized.semanticStatus,
|
|
651
|
+
...input?.universalAstMetadata
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
const readiness = classifyNativeImportReadiness(losses, {
|
|
655
|
+
evidence,
|
|
656
|
+
parser: context.parser,
|
|
657
|
+
scanKind: 'external-semantic-index',
|
|
658
|
+
semanticStatus: normalized.semanticStatus
|
|
659
|
+
});
|
|
660
|
+
return {
|
|
661
|
+
kind: 'frontier.lang.externalSemanticIndexImport',
|
|
662
|
+
version: 1,
|
|
663
|
+
id: input?.id ?? `external_semantic_index_${idPart}_${idFragment(format)}`,
|
|
664
|
+
format,
|
|
665
|
+
language: context.language,
|
|
666
|
+
sourcePath: context.sourcePath,
|
|
667
|
+
projectRoot: context.projectRoot,
|
|
668
|
+
semanticIndex,
|
|
669
|
+
universalAst,
|
|
670
|
+
sourceMaps,
|
|
671
|
+
losses,
|
|
672
|
+
evidence,
|
|
673
|
+
readiness,
|
|
674
|
+
summary: {
|
|
675
|
+
documents: semanticIndex.documents.length,
|
|
676
|
+
symbols: semanticIndex.symbols.length,
|
|
677
|
+
occurrences: semanticIndex.occurrences.length,
|
|
678
|
+
relations: semanticIndex.relations.length,
|
|
679
|
+
facts: semanticIndex.facts.length,
|
|
680
|
+
sourceMapMappings: sourceMaps.reduce((sum, sourceMap) => sum + (sourceMap.mappings?.length ?? 0), 0),
|
|
681
|
+
losses: losses.length,
|
|
682
|
+
readiness: readiness.readiness
|
|
683
|
+
},
|
|
684
|
+
metadata: {
|
|
685
|
+
format,
|
|
686
|
+
parser: context.parser,
|
|
687
|
+
semanticStatus: normalized.semanticStatus,
|
|
688
|
+
payloadHash: hashSemanticValue(payload),
|
|
689
|
+
...normalized.metadata,
|
|
690
|
+
...context.metadata
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function normalizeExternalSemanticIndexFormat(format) {
|
|
696
|
+
const normalized = String(format ?? 'frontier-semantic-index').trim().toLowerCase();
|
|
697
|
+
const aliases = {
|
|
698
|
+
frontier: 'frontier-semantic-index',
|
|
699
|
+
'frontier.semantic-index': 'frontier-semantic-index',
|
|
700
|
+
'frontier.lang.semanticindex': 'frontier-semantic-index',
|
|
701
|
+
scipindex: 'scip',
|
|
702
|
+
'sourcegraph-scip': 'scip',
|
|
703
|
+
lsp: 'lsp',
|
|
704
|
+
'language-server-protocol': 'lsp',
|
|
705
|
+
semanticdb: 'semanticdb',
|
|
706
|
+
'scala-semanticdb': 'semanticdb'
|
|
707
|
+
};
|
|
708
|
+
return aliases[normalized] ?? normalized;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function inferExternalSemanticIndexFormat(payload) {
|
|
712
|
+
if (payload.kind === 'frontier.lang.semanticIndex') return 'frontier-semantic-index';
|
|
713
|
+
if (Array.isArray(payload) && payload.some((entry) => entry?.type === 'vertex' || entry?.type === 'edge')) return 'lsif';
|
|
714
|
+
if (Array.isArray(payload.vertices) || Array.isArray(payload.edges)) return 'lsif';
|
|
715
|
+
if (Array.isArray(payload.documents) && payload.documents.some((document) => document?.relative_path ?? document?.relativePath)) return 'scip';
|
|
716
|
+
if (payload.metadata?.project_root || payload.metadata?.projectRoot || payload.external_symbols || payload.externalSymbols) return 'scip';
|
|
717
|
+
if (Array.isArray(payload.documents) && payload.documents.some((document) => document?.symbols && document?.occurrences && (document?.uri || document?.md5 || document?.schema))) return 'semanticdb';
|
|
718
|
+
if (Array.isArray(payload.documentSymbols) || Array.isArray(payload.symbols) || payload.semanticTokens || payload.location || payload.range) return 'lsp';
|
|
719
|
+
return 'frontier-semantic-index';
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function normalizeExternalSemanticIndexPayload(payload, context) {
|
|
723
|
+
if (context.format === 'frontier-semantic-index') return normalizeFrontierSemanticIndexPayload(payload, context);
|
|
724
|
+
if (context.format === 'scip') return normalizeScipPayload(payload, context);
|
|
725
|
+
if (context.format === 'lsif') return normalizeLsifPayload(payload, context);
|
|
726
|
+
if (context.format === 'lsp') return normalizeLspPayload(payload, context);
|
|
727
|
+
if (context.format === 'semanticdb') return normalizeSemanticDbPayload(payload, context);
|
|
728
|
+
return normalizeGenericExternalSemanticIndexPayload(payload, context);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function externalSemanticBase(context, metadata = {}) {
|
|
732
|
+
return {
|
|
733
|
+
repository: context.projectRoot ? { root: context.projectRoot } : undefined,
|
|
734
|
+
documents: [],
|
|
735
|
+
symbols: [],
|
|
736
|
+
occurrences: [],
|
|
737
|
+
relations: [],
|
|
738
|
+
facts: [],
|
|
739
|
+
evidence: [externalSemanticEvidence(context, 'passed', `Imported ${context.format} semantic index payload.`)],
|
|
740
|
+
losses: [externalSemanticCoverageLoss(context)],
|
|
741
|
+
semanticStatus: 'external-semantic-index',
|
|
742
|
+
metadata
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function normalizeFrontierSemanticIndexPayload(payload, context) {
|
|
747
|
+
const result = externalSemanticBase(context, { sourceFormat: payload.kind ?? 'frontier.lang.semanticIndex' });
|
|
748
|
+
result.repository = payload.repository ?? result.repository;
|
|
749
|
+
result.documents = normalizeArray(payload.documents).map((document, index) => externalDocument(document, context, index));
|
|
750
|
+
result.symbols = normalizeArray(payload.symbols).map((symbol, index) => externalSymbol(symbol, context, index));
|
|
751
|
+
result.occurrences = normalizeArray(payload.occurrences).map((occurrence, index) => externalOccurrence(occurrence, context, index));
|
|
752
|
+
result.relations = normalizeArray(payload.relations).map((relation, index) => externalRelation(relation, context, index));
|
|
753
|
+
result.facts = normalizeArray(payload.facts).map((fact, index) => externalFact(fact, context, index));
|
|
754
|
+
result.evidence = uniqueByEvidenceId([...(payload.evidence ?? []), ...result.evidence]);
|
|
755
|
+
if (payload.metadata) result.metadata = { ...result.metadata, ...payload.metadata };
|
|
756
|
+
return withExternalEmptyLoss(result, context);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function normalizeScipPayload(payload, context) {
|
|
760
|
+
const result = externalSemanticBase(context, { sourceFormat: 'scip' });
|
|
761
|
+
const metadata = payload.metadata ?? {};
|
|
762
|
+
const projectRoot = context.projectRoot ?? metadata.project_root ?? metadata.projectRoot;
|
|
763
|
+
result.repository = projectRoot ? { root: projectRoot } : undefined;
|
|
764
|
+
const documents = normalizeArray(payload.documents);
|
|
765
|
+
for (const [documentIndex, document] of documents.entries()) {
|
|
766
|
+
const path = document.relative_path ?? document.relativePath ?? document.path ?? context.sourcePath ?? `scip-document-${documentIndex + 1}`;
|
|
767
|
+
const language = normalizeExternalSemanticLanguage(document.language ?? context.language);
|
|
768
|
+
const documentId = document.id ?? `doc_${idFragment(path)}`;
|
|
769
|
+
result.documents.push({
|
|
770
|
+
id: documentId,
|
|
771
|
+
path,
|
|
772
|
+
language,
|
|
773
|
+
sourceHash: document.sourceHash ?? document.md5 ?? context.sourceHash,
|
|
774
|
+
metadata: {
|
|
775
|
+
format: 'scip',
|
|
776
|
+
projectRoot,
|
|
777
|
+
textDocumentEncoding: metadata.text_document_encoding ?? metadata.textDocumentEncoding,
|
|
778
|
+
documentIndex
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
const documentSymbols = new Map();
|
|
782
|
+
for (const symbolInfo of [...normalizeArray(document.symbols), ...normalizeArray(payload.external_symbols ?? payload.externalSymbols)]) {
|
|
783
|
+
const symbolId = scipSymbolId(symbolInfo.symbol, context, normalizeArray(document.symbols).includes(symbolInfo) ? documentId : undefined);
|
|
784
|
+
if (!symbolId || documentSymbols.has(symbolId)) continue;
|
|
785
|
+
documentSymbols.set(symbolId, true);
|
|
786
|
+
result.symbols.push({
|
|
787
|
+
id: symbolId,
|
|
788
|
+
scheme: 'scip',
|
|
789
|
+
name: symbolInfo.display_name ?? symbolInfo.displayName ?? nameFromExternalSymbol(symbolInfo.symbol),
|
|
790
|
+
kind: normalizeExternalSymbolKind(symbolInfo.kind),
|
|
791
|
+
language,
|
|
792
|
+
signatureHash: hashSemanticValue([symbolInfo.symbol, symbolInfo.signature_documentation ?? symbolInfo.signatureDocumentation]),
|
|
793
|
+
metadata: {
|
|
794
|
+
format: 'scip',
|
|
795
|
+
rawSymbol: symbolInfo.symbol,
|
|
796
|
+
documentation: symbolInfo.documentation,
|
|
797
|
+
enclosingSymbol: symbolInfo.enclosing_symbol ?? symbolInfo.enclosingSymbol,
|
|
798
|
+
external: !normalizeArray(document.symbols).includes(symbolInfo)
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
result.facts.push(...scipSymbolFacts(symbolInfo, symbolId));
|
|
802
|
+
result.relations.push(...scipRelationshipRelations(symbolInfo, symbolId, context));
|
|
803
|
+
}
|
|
804
|
+
for (const [occurrenceIndex, occurrence] of normalizeArray(document.occurrences).entries()) {
|
|
805
|
+
const symbolId = scipSymbolId(occurrence.symbol, context, documentId);
|
|
806
|
+
if (!symbolId) continue;
|
|
807
|
+
const role = scipOccurrenceRole(occurrence.symbol_roles ?? occurrence.symbolRoles);
|
|
808
|
+
if (!documentSymbols.has(symbolId)) {
|
|
809
|
+
documentSymbols.set(symbolId, true);
|
|
810
|
+
result.symbols.push({
|
|
811
|
+
id: symbolId,
|
|
812
|
+
scheme: 'scip',
|
|
813
|
+
name: nameFromExternalSymbol(occurrence.symbol),
|
|
814
|
+
kind: scipSyntaxKind(occurrence.syntax_kind ?? occurrence.syntaxKind),
|
|
815
|
+
language,
|
|
816
|
+
metadata: { format: 'scip', rawSymbol: occurrence.symbol, inferredFromOccurrence: true }
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
result.occurrences.push({
|
|
820
|
+
id: occurrence.id ?? `occ_${idFragment(documentId)}_${occurrenceIndex + 1}`,
|
|
821
|
+
documentId,
|
|
822
|
+
symbolId,
|
|
823
|
+
role,
|
|
824
|
+
span: spanFromScipOccurrence(occurrence, path, context.sourceHash),
|
|
825
|
+
metadata: {
|
|
826
|
+
format: 'scip',
|
|
827
|
+
symbolRoles: occurrence.symbol_roles ?? occurrence.symbolRoles,
|
|
828
|
+
roleSet: scipOccurrenceRoleSet(occurrence.symbol_roles ?? occurrence.symbolRoles),
|
|
829
|
+
syntaxKind: occurrence.syntax_kind ?? occurrence.syntaxKind,
|
|
830
|
+
overrideDocumentation: occurrence.override_documentation ?? occurrence.overrideDocumentation
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
for (const diagnostic of normalizeArray(occurrence.diagnostics)) {
|
|
834
|
+
const scopedDiagnostic = { ...diagnostic, range: diagnostic.range ?? occurrence.range };
|
|
835
|
+
result.facts.push(externalDiagnosticFact(scopedDiagnostic, context, documentId, path, result.facts.length));
|
|
836
|
+
result.losses.push(externalDiagnosticLoss(scopedDiagnostic, context, path));
|
|
837
|
+
}
|
|
838
|
+
if (scipOccurrenceRoleSet(occurrence.symbol_roles ?? occurrence.symbolRoles).includes('generated')) {
|
|
839
|
+
result.losses.push({
|
|
840
|
+
id: `loss_${idFragment(documentId)}_${occurrenceIndex + 1}_generated_scip_occurrence`,
|
|
841
|
+
severity: 'warning',
|
|
842
|
+
phase: 'index',
|
|
843
|
+
sourceFormat: 'scip',
|
|
844
|
+
kind: 'generatedCode',
|
|
845
|
+
message: 'SCIP occurrence is marked generated; merge admission should review generated/source ownership before applying patches.',
|
|
846
|
+
span: spanFromScipOccurrence(occurrence, path, context.sourceHash),
|
|
847
|
+
semanticSymbolId: symbolId,
|
|
848
|
+
metadata: { format: 'scip', symbolRoles: occurrence.symbol_roles ?? occurrence.symbolRoles }
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return withExternalEmptyLoss(result, context);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function normalizeLsifPayload(payload, context) {
|
|
857
|
+
const result = externalSemanticBase(context, { sourceFormat: 'lsif' });
|
|
858
|
+
const records = Array.isArray(payload) ? payload : [...normalizeArray(payload.vertices), ...normalizeArray(payload.edges)];
|
|
859
|
+
const vertices = new Map(records.filter((record) => record?.type === 'vertex').map((record) => [record.id, record]));
|
|
860
|
+
const edges = records.filter((record) => record?.type === 'edge');
|
|
861
|
+
const documentByVertex = new Map();
|
|
862
|
+
const documentIdByRange = new Map();
|
|
863
|
+
const resultSetByRange = new Map();
|
|
864
|
+
const monikerByOut = new Map();
|
|
865
|
+
const definitionRangeIds = new Set();
|
|
866
|
+
for (const vertex of vertices.values()) {
|
|
867
|
+
if (vertex.label === 'document') {
|
|
868
|
+
const path = uriToPath(vertex.uri) ?? vertex.uri ?? context.sourcePath ?? `lsif-document-${result.documents.length + 1}`;
|
|
869
|
+
const documentId = `doc_${idFragment(vertex.id ?? path)}`;
|
|
870
|
+
documentByVertex.set(vertex.id, { id: documentId, path, language: normalizeExternalSemanticLanguage(vertex.languageId ?? context.language) });
|
|
871
|
+
result.documents.push({
|
|
872
|
+
id: documentId,
|
|
873
|
+
path,
|
|
874
|
+
language: normalizeExternalSemanticLanguage(vertex.languageId ?? context.language),
|
|
875
|
+
metadata: { format: 'lsif', vertexId: vertex.id, uri: vertex.uri }
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
if (vertex.label === 'moniker') monikerByOut.set(vertex.id, vertex);
|
|
879
|
+
}
|
|
880
|
+
for (const edge of edges) {
|
|
881
|
+
if (edge.label === 'next') resultSetByRange.set(edge.outV, edge.inV);
|
|
882
|
+
if (edge.label === 'moniker') monikerByOut.set(edge.outV, vertices.get(edge.inV) ?? edge);
|
|
883
|
+
if (edge.label === 'contains') {
|
|
884
|
+
const document = documentByVertex.get(edge.outV);
|
|
885
|
+
if (document) {
|
|
886
|
+
for (const rangeId of normalizeArray(edge.inVs ?? edge.inV)) {
|
|
887
|
+
documentIdByRange.set(rangeId, document.id);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (edge.label === 'item' && (edge.property === 'definitions' || edge.property === 'declarations')) {
|
|
892
|
+
for (const rangeId of normalizeArray(edge.inVs ?? edge.inV)) definitionRangeIds.add(rangeId);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
const documentIds = result.documents.map((document) => document.id);
|
|
896
|
+
const defaultDocument = result.documents[0] ?? {
|
|
897
|
+
id: `doc_${idFragment(context.sourcePath ?? 'lsif')}`,
|
|
898
|
+
path: context.sourcePath ?? 'lsif:memory',
|
|
899
|
+
language: context.language
|
|
900
|
+
};
|
|
901
|
+
if (!result.documents.length) result.documents.push(defaultDocument);
|
|
902
|
+
for (const [vertexId, vertex] of vertices.entries()) {
|
|
903
|
+
if (vertex.label !== 'range') continue;
|
|
904
|
+
const resultSetId = resultSetByRange.get(vertexId);
|
|
905
|
+
const moniker = monikerByOut.get(resultSetId) ?? monikerByOut.get(vertexId);
|
|
906
|
+
const symbolId = moniker?.identifier
|
|
907
|
+
? `symbol:lsif:${idFragment(moniker.scheme ?? moniker.kind ?? 'moniker')}:${idFragment(moniker.identifier)}`
|
|
908
|
+
: `symbol:lsif:${idFragment(resultSetId ?? vertexId)}`;
|
|
909
|
+
const documentId = documentIdByRange.get(vertexId) ?? documentIds[0] ?? defaultDocument.id;
|
|
910
|
+
const owningDocument = result.documents.find((document) => document.id === documentId) ?? defaultDocument;
|
|
911
|
+
const span = spanFromLspRange(vertex, owningDocument.path, context.sourceHash, 0);
|
|
912
|
+
if (!result.symbols.some((symbol) => symbol.id === symbolId)) {
|
|
913
|
+
result.symbols.push({
|
|
914
|
+
id: symbolId,
|
|
915
|
+
scheme: 'lsif',
|
|
916
|
+
name: moniker?.identifier ?? `range:${vertexId}`,
|
|
917
|
+
kind: moniker?.kind ?? 'symbol',
|
|
918
|
+
language: owningDocument.language,
|
|
919
|
+
definitionSpan: definitionRangeIds.has(vertexId) ? span : undefined,
|
|
920
|
+
metadata: { format: 'lsif', resultSetId, moniker }
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
result.occurrences.push({
|
|
924
|
+
id: `occ_${idFragment(vertexId)}`,
|
|
925
|
+
documentId,
|
|
926
|
+
symbolId,
|
|
927
|
+
role: definitionRangeIds.has(vertexId) ? 'definition' : 'reference',
|
|
928
|
+
span,
|
|
929
|
+
metadata: { format: 'lsif', vertexId, resultSetId }
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
for (const edge of edges) {
|
|
933
|
+
if (edge.label === 'textDocument/definition' || edge.label === 'textDocument/references' || edge.label === 'textDocument/declaration') {
|
|
934
|
+
result.relations.push({
|
|
935
|
+
id: `rel_${idFragment(edge.id ?? `${edge.outV}_${edge.inV}_${edge.label}`)}`,
|
|
936
|
+
sourceId: `lsif:${edge.outV}`,
|
|
937
|
+
predicate: edge.label,
|
|
938
|
+
targetId: `lsif:${edge.inV}`,
|
|
939
|
+
metadata: { format: 'lsif', edge }
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return withExternalEmptyLoss(result, context);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function normalizeLspPayload(payload, context) {
|
|
947
|
+
const result = externalSemanticBase(context, { sourceFormat: 'lsp' });
|
|
948
|
+
const documents = normalizeLspDocuments(payload, context);
|
|
949
|
+
for (const [documentIndex, document] of documents.entries()) {
|
|
950
|
+
const sourcePath = uriToPath(document.uri) ?? document.sourcePath ?? document.path ?? context.sourcePath ?? `lsp-document-${documentIndex + 1}`;
|
|
951
|
+
const language = normalizeExternalSemanticLanguage(document.languageId ?? document.language ?? context.language);
|
|
952
|
+
const documentId = document.id ?? `doc_${idFragment(sourcePath)}`;
|
|
953
|
+
result.documents.push({
|
|
954
|
+
id: documentId,
|
|
955
|
+
path: sourcePath,
|
|
956
|
+
language,
|
|
957
|
+
sourceHash: document.sourceHash ?? context.sourceHash,
|
|
958
|
+
metadata: { format: 'lsp', uri: document.uri, documentIndex }
|
|
959
|
+
});
|
|
960
|
+
const symbols = normalizeArray(document.documentSymbols ?? document.symbols ?? payload.documentSymbols ?? payload.symbols);
|
|
961
|
+
for (const symbol of symbols) addLspSymbol(result, symbol, {
|
|
962
|
+
context,
|
|
963
|
+
documentId,
|
|
964
|
+
sourcePath,
|
|
965
|
+
language,
|
|
966
|
+
parentName: symbol.containerName
|
|
967
|
+
});
|
|
968
|
+
const semanticTokens = document.semanticTokens ?? payload.semanticTokens;
|
|
969
|
+
if (semanticTokens) addLspSemanticTokens(result, semanticTokens, { context, documentId, sourcePath, language });
|
|
970
|
+
for (const diagnostic of normalizeArray(document.diagnostics ?? payload.diagnostics)) {
|
|
971
|
+
result.facts.push(externalDiagnosticFact(diagnostic, context, documentId, sourcePath, result.facts.length));
|
|
972
|
+
result.losses.push(externalDiagnosticLoss(diagnostic, context, sourcePath));
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return withExternalEmptyLoss(result, context);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function normalizeSemanticDbPayload(payload, context) {
|
|
979
|
+
const result = externalSemanticBase(context, { sourceFormat: 'semanticdb' });
|
|
980
|
+
const documents = normalizeArray(payload.documents ?? payload.textDocuments ?? payload);
|
|
981
|
+
for (const [documentIndex, document] of documents.entries()) {
|
|
982
|
+
const sourcePath = uriToPath(document.uri) ?? document.uri ?? document.path ?? context.sourcePath ?? `semanticdb-document-${documentIndex + 1}`;
|
|
983
|
+
const language = normalizeExternalSemanticLanguage(document.language ?? context.language ?? 'scala');
|
|
984
|
+
const documentId = document.id ?? `doc_${idFragment(sourcePath)}`;
|
|
985
|
+
result.documents.push({
|
|
986
|
+
id: documentId,
|
|
987
|
+
path: sourcePath,
|
|
988
|
+
language,
|
|
989
|
+
sourceHash: document.md5 ?? document.sourceHash ?? context.sourceHash,
|
|
990
|
+
metadata: { format: 'semanticdb', schema: document.schema, documentIndex }
|
|
991
|
+
});
|
|
992
|
+
for (const [symbolIndex, symbolInfo] of normalizeArray(document.symbols).entries()) {
|
|
993
|
+
const symbolId = semanticDbSymbolId(symbolInfo.symbol, context, documentId);
|
|
994
|
+
result.symbols.push({
|
|
995
|
+
id: symbolId,
|
|
996
|
+
scheme: 'semanticdb',
|
|
997
|
+
name: symbolInfo.display_name ?? symbolInfo.displayName ?? nameFromExternalSymbol(symbolInfo.symbol),
|
|
998
|
+
kind: normalizeExternalSymbolKind(symbolInfo.kind),
|
|
999
|
+
language,
|
|
1000
|
+
signatureHash: hashSemanticValue(symbolInfo.signature ?? symbolInfo.signature_documentation ?? symbolInfo.signatureDocumentation ?? symbolInfo),
|
|
1001
|
+
metadata: { format: 'semanticdb', symbolIndex, rawSymbol: symbolInfo.symbol, properties: symbolInfo.properties }
|
|
1002
|
+
});
|
|
1003
|
+
result.facts.push(...semanticDbSymbolFacts(symbolInfo, symbolId));
|
|
1004
|
+
}
|
|
1005
|
+
for (const [occurrenceIndex, occurrence] of normalizeArray(document.occurrences).entries()) {
|
|
1006
|
+
const symbolId = semanticDbSymbolId(occurrence.symbol, context, documentId);
|
|
1007
|
+
if (!result.symbols.some((symbol) => symbol.id === symbolId)) {
|
|
1008
|
+
result.symbols.push({
|
|
1009
|
+
id: symbolId,
|
|
1010
|
+
scheme: 'semanticdb',
|
|
1011
|
+
name: nameFromExternalSymbol(occurrence.symbol),
|
|
1012
|
+
kind: 'symbol',
|
|
1013
|
+
language,
|
|
1014
|
+
metadata: { format: 'semanticdb', inferredFromOccurrence: true, rawSymbol: occurrence.symbol }
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
result.occurrences.push({
|
|
1018
|
+
id: occurrence.id ?? `occ_${idFragment(documentId)}_${occurrenceIndex + 1}`,
|
|
1019
|
+
documentId,
|
|
1020
|
+
symbolId,
|
|
1021
|
+
role: semanticDbOccurrenceRole(occurrence.role),
|
|
1022
|
+
span: spanFromSemanticDbRange(occurrence.range, sourcePath, document.md5 ?? context.sourceHash),
|
|
1023
|
+
metadata: { format: 'semanticdb', role: occurrence.role }
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
for (const diagnostic of normalizeArray(document.diagnostics)) {
|
|
1027
|
+
result.facts.push(externalDiagnosticFact(diagnostic, context, documentId, sourcePath, result.facts.length));
|
|
1028
|
+
result.losses.push(externalDiagnosticLoss(diagnostic, context, sourcePath));
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return withExternalEmptyLoss(result, context);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function normalizeGenericExternalSemanticIndexPayload(payload, context) {
|
|
1035
|
+
const result = externalSemanticBase(context, { sourceFormat: context.format, genericPayload: true });
|
|
1036
|
+
result.losses.push({
|
|
1037
|
+
id: `loss_${context.idPart}_${idFragment(context.format)}_unsupported_payload`,
|
|
1038
|
+
severity: 'warning',
|
|
1039
|
+
phase: 'index',
|
|
1040
|
+
sourceFormat: context.format,
|
|
1041
|
+
kind: 'unsupportedSemantic',
|
|
1042
|
+
message: `External semantic index format ${context.format} is not recognized; payload hash is preserved as evidence only.`,
|
|
1043
|
+
metadata: { format: context.format, payloadHash: hashSemanticValue(payload) }
|
|
1044
|
+
});
|
|
1045
|
+
return result;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function normalizeArray(value) {
|
|
1049
|
+
if (value === undefined || value === null) return [];
|
|
1050
|
+
return Array.isArray(value) ? value : [value];
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function normalizeExternalSemanticLanguage(value) {
|
|
1054
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
1055
|
+
const raw = typeof value === 'number' ? externalLanguageNameByNumber[value] : String(value);
|
|
1056
|
+
return normalizeNativeLanguageId(raw);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function externalDocument(document, context, index) {
|
|
1060
|
+
const path = document.path ?? document.uri ?? document.relative_path ?? document.relativePath ?? context.sourcePath ?? `external-document-${index + 1}`;
|
|
1061
|
+
return {
|
|
1062
|
+
id: document.id ?? `doc_${idFragment(path)}`,
|
|
1063
|
+
path: uriToPath(path) ?? path,
|
|
1064
|
+
language: normalizeExternalSemanticLanguage(document.language ?? document.languageId ?? context.language),
|
|
1065
|
+
sourceHash: document.sourceHash ?? document.md5 ?? context.sourceHash,
|
|
1066
|
+
metadata: { format: context.format, ...document.metadata }
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function externalSymbol(symbol, context, index) {
|
|
1071
|
+
const id = symbol.id ?? symbol.symbolId ?? symbol.symbol ?? `symbol:${context.format}:${index + 1}`;
|
|
1072
|
+
return {
|
|
1073
|
+
...symbol,
|
|
1074
|
+
id: String(id),
|
|
1075
|
+
scheme: symbol.scheme ?? context.format,
|
|
1076
|
+
name: symbol.name ?? symbol.display_name ?? symbol.displayName ?? nameFromExternalSymbol(id),
|
|
1077
|
+
kind: normalizeExternalSymbolKind(symbol.kind),
|
|
1078
|
+
language: normalizeExternalSemanticLanguage(symbol.language ?? context.language),
|
|
1079
|
+
definitionSpan: normalizeExternalSpan(symbol.definitionSpan ?? symbol.span, context.sourcePath, context.sourceHash),
|
|
1080
|
+
metadata: { format: context.format, rawSymbol: symbol.symbol, ...symbol.metadata }
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function externalOccurrence(occurrence, context, index) {
|
|
1085
|
+
return {
|
|
1086
|
+
...occurrence,
|
|
1087
|
+
id: occurrence.id ?? `occ_${context.idPart}_${index + 1}`,
|
|
1088
|
+
documentId: occurrence.documentId ?? occurrence.document_id ?? `doc_${idFragment(occurrence.path ?? context.sourcePath ?? context.format)}`,
|
|
1089
|
+
symbolId: occurrence.symbolId ?? occurrence.symbol_id ?? occurrence.symbol ?? `symbol:${context.format}:unknown`,
|
|
1090
|
+
role: normalizeExternalOccurrenceRole(occurrence.role),
|
|
1091
|
+
span: normalizeExternalSpan(occurrence.span ?? occurrence.range, occurrence.path ?? context.sourcePath, context.sourceHash),
|
|
1092
|
+
metadata: { format: context.format, ...occurrence.metadata }
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function externalRelation(relation, context, index) {
|
|
1097
|
+
return {
|
|
1098
|
+
...relation,
|
|
1099
|
+
id: relation.id ?? `rel_${context.idPart}_${index + 1}`,
|
|
1100
|
+
sourceId: relation.sourceId ?? relation.source_id ?? relation.subjectId ?? relation.subject_id ?? `external:${context.format}`,
|
|
1101
|
+
predicate: relation.predicate ?? relation.label ?? relation.kind ?? 'related',
|
|
1102
|
+
targetId: relation.targetId ?? relation.target_id ?? relation.objectId ?? relation.object_id ?? relation.symbol ?? `external:${context.format}`,
|
|
1103
|
+
metadata: { format: context.format, ...relation.metadata }
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function externalFact(fact, context, index) {
|
|
1108
|
+
return {
|
|
1109
|
+
...fact,
|
|
1110
|
+
id: fact.id ?? `fact_${context.idPart}_${index + 1}`,
|
|
1111
|
+
predicate: fact.predicate ?? fact.kind ?? 'externalFact',
|
|
1112
|
+
subjectId: fact.subjectId ?? fact.subject_id ?? fact.symbolId ?? fact.symbol_id ?? `external:${context.format}`,
|
|
1113
|
+
value: fact.value ?? fact.data ?? fact,
|
|
1114
|
+
metadata: { format: context.format, ...fact.metadata }
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function externalSemanticEvidence(context, status, summary, metadata = {}) {
|
|
1119
|
+
return {
|
|
1120
|
+
id: `evidence_${context.idPart}_${idFragment(context.format)}_external_semantic_index`,
|
|
1121
|
+
kind: 'import',
|
|
1122
|
+
status,
|
|
1123
|
+
path: context.sourcePath,
|
|
1124
|
+
summary,
|
|
1125
|
+
metadata: {
|
|
1126
|
+
format: context.format,
|
|
1127
|
+
parser: context.parser,
|
|
1128
|
+
projectRoot: context.projectRoot,
|
|
1129
|
+
...metadata
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function externalSemanticCoverageLoss(context) {
|
|
1135
|
+
return {
|
|
1136
|
+
id: `loss_${context.idPart}_${idFragment(context.format)}_partial_semantic_index`,
|
|
1137
|
+
severity: 'info',
|
|
1138
|
+
phase: 'index',
|
|
1139
|
+
sourceFormat: context.format,
|
|
1140
|
+
kind: 'partialSemanticIndex',
|
|
1141
|
+
message: `${context.format} payload imported symbols, occurrences, and facts as external semantic evidence; full parser AST, comments, trivia, and executable semantics still require a native parser adapter.`,
|
|
1142
|
+
semanticIndexId: context.semanticIndexId,
|
|
1143
|
+
metadata: {
|
|
1144
|
+
format: context.format,
|
|
1145
|
+
parser: context.parser,
|
|
1146
|
+
source: 'external-semantic-index'
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
function withExternalEmptyLoss(result, context) {
|
|
1152
|
+
if (!result.documents.length) {
|
|
1153
|
+
result.documents.push({
|
|
1154
|
+
id: `doc_${context.idPart}_${idFragment(context.format)}`,
|
|
1155
|
+
path: context.sourcePath ?? `${context.format}:memory`,
|
|
1156
|
+
language: context.language,
|
|
1157
|
+
sourceHash: context.sourceHash,
|
|
1158
|
+
metadata: { format: context.format, inferred: true }
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
if (!result.symbols.length && !result.occurrences.length) {
|
|
1162
|
+
result.losses.push({
|
|
1163
|
+
id: `loss_${context.idPart}_${idFragment(context.format)}_empty_semantic_index`,
|
|
1164
|
+
severity: 'warning',
|
|
1165
|
+
phase: 'index',
|
|
1166
|
+
sourceFormat: context.format,
|
|
1167
|
+
kind: 'partialSemanticIndex',
|
|
1168
|
+
message: `${context.format} payload did not contain symbols or occurrences that Frontier can map.`,
|
|
1169
|
+
metadata: { format: context.format }
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
attachExternalOwnership(result, context);
|
|
1173
|
+
result.symbols = uniqueRecordsById(result.symbols);
|
|
1174
|
+
result.occurrences = uniqueRecordsById(result.occurrences);
|
|
1175
|
+
result.relations = uniqueRecordsById(result.relations);
|
|
1176
|
+
result.facts = uniqueRecordsById(result.facts);
|
|
1177
|
+
result.losses = uniqueByLossId(result.losses);
|
|
1178
|
+
result.evidence = uniqueByEvidenceId(result.evidence);
|
|
1179
|
+
return result;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function attachExternalOwnership(result, context) {
|
|
1183
|
+
const occurrencesBySymbol = new Map();
|
|
1184
|
+
for (const occurrence of result.occurrences) {
|
|
1185
|
+
if (!occurrencesBySymbol.has(occurrence.symbolId)) occurrencesBySymbol.set(occurrence.symbolId, []);
|
|
1186
|
+
occurrencesBySymbol.get(occurrence.symbolId).push(occurrence);
|
|
1187
|
+
result.relations.push({
|
|
1188
|
+
id: `rel_${idFragment(occurrence.documentId)}_${idFragment(occurrence.id)}_${idFragment(occurrence.role)}`,
|
|
1189
|
+
sourceId: occurrence.documentId,
|
|
1190
|
+
predicate: externalRelationPredicateForOccurrence(occurrence),
|
|
1191
|
+
targetId: occurrence.symbolId,
|
|
1192
|
+
metadata: {
|
|
1193
|
+
format: context.format,
|
|
1194
|
+
source: 'external-semantic-index',
|
|
1195
|
+
occurrenceId: occurrence.id,
|
|
1196
|
+
role: occurrence.role
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
result.symbols = result.symbols.map((symbol) => {
|
|
1201
|
+
const occurrences = occurrencesBySymbol.get(symbol.id) ?? [];
|
|
1202
|
+
const definition = occurrences.find((occurrence) => occurrence.role === 'definition') ?? occurrences[0];
|
|
1203
|
+
const sourceSpan = symbol.definitionSpan ?? definition?.span;
|
|
1204
|
+
const regionKind = semanticRegionKindForSymbol(symbol, undefined, undefined);
|
|
1205
|
+
const key = [
|
|
1206
|
+
'external',
|
|
1207
|
+
symbol.language ?? context.language ?? 'unknown',
|
|
1208
|
+
sourceSpan?.path ?? context.sourcePath ?? 'memory',
|
|
1209
|
+
regionKind,
|
|
1210
|
+
symbol.name ?? symbol.id
|
|
1211
|
+
].join('#');
|
|
1212
|
+
const region = {
|
|
1213
|
+
id: `region_${idFragment(key)}`,
|
|
1214
|
+
key,
|
|
1215
|
+
regionKind,
|
|
1216
|
+
granularity: 'symbol',
|
|
1217
|
+
language: symbol.language ?? context.language,
|
|
1218
|
+
documentId: definition?.documentId,
|
|
1219
|
+
sourcePath: sourceSpan?.path ?? context.sourcePath,
|
|
1220
|
+
sourceHash: context.sourceHash,
|
|
1221
|
+
symbolId: symbol.id,
|
|
1222
|
+
symbolName: symbol.name,
|
|
1223
|
+
symbolKind: symbol.kind,
|
|
1224
|
+
sourceSpan,
|
|
1225
|
+
precision: sourceSpan ? 'declaration' : 'unknown',
|
|
1226
|
+
mergePolicy: semanticRegionMergePolicy(regionKind),
|
|
1227
|
+
metadata: {
|
|
1228
|
+
format: context.format,
|
|
1229
|
+
source: 'external-semantic-index'
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
result.facts.push({
|
|
1233
|
+
id: `fact_${idFragment(symbol.id)}_ownership_region`,
|
|
1234
|
+
predicate: 'semanticOwnershipRegion',
|
|
1235
|
+
subjectId: symbol.id,
|
|
1236
|
+
value: region
|
|
1237
|
+
}, {
|
|
1238
|
+
id: `fact_${idFragment(symbol.id)}_ownership_region_taxonomy`,
|
|
1239
|
+
predicate: 'semanticOwnershipRegionTaxonomy',
|
|
1240
|
+
subjectId: symbol.id,
|
|
1241
|
+
value: {
|
|
1242
|
+
regionKind: region.regionKind,
|
|
1243
|
+
granularity: region.granularity,
|
|
1244
|
+
key: region.key
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
return {
|
|
1248
|
+
...symbol,
|
|
1249
|
+
definitionSpan: symbol.definitionSpan ?? definition?.span,
|
|
1250
|
+
metadata: {
|
|
1251
|
+
...symbol.metadata,
|
|
1252
|
+
ownershipRegionId: symbol.metadata?.ownershipRegionId ?? region.id,
|
|
1253
|
+
ownershipRegionKey: symbol.metadata?.ownershipRegionKey ?? region.key,
|
|
1254
|
+
ownershipRegionKind: symbol.metadata?.ownershipRegionKind ?? region.regionKind
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
function externalRelationPredicateForOccurrence(occurrence) {
|
|
1261
|
+
const role = String(occurrence.role ?? '').toLowerCase();
|
|
1262
|
+
if (role === 'definition' || role === 'declaration') return 'defines';
|
|
1263
|
+
if (role === 'import') return 'imports';
|
|
1264
|
+
if (role === 'write') return 'writes';
|
|
1265
|
+
if (role === 'read') return 'reads';
|
|
1266
|
+
return 'references';
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function externalSemanticSourceMapMappings(semanticIndex, context) {
|
|
1270
|
+
const symbolsById = new Map((semanticIndex.symbols ?? []).map((symbol) => [symbol.id, symbol]));
|
|
1271
|
+
const evidenceIds = (context.evidence ?? []).map((record) => record.id).filter(Boolean);
|
|
1272
|
+
const lossIds = (context.losses ?? []).map((loss) => loss.id).filter(Boolean);
|
|
1273
|
+
return (semanticIndex.occurrences ?? [])
|
|
1274
|
+
.filter((occurrence) => occurrence.span)
|
|
1275
|
+
.map((occurrence, index) => {
|
|
1276
|
+
const symbol = symbolsById.get(occurrence.symbolId);
|
|
1277
|
+
return {
|
|
1278
|
+
id: `map_${idFragment(occurrence.id ?? `${occurrence.symbolId}_${index + 1}`)}`,
|
|
1279
|
+
semanticSymbolId: occurrence.symbolId,
|
|
1280
|
+
semanticOccurrenceId: occurrence.id,
|
|
1281
|
+
sourceSpan: occurrence.span,
|
|
1282
|
+
evidenceIds,
|
|
1283
|
+
lossIds,
|
|
1284
|
+
ownershipRegionId: symbol?.metadata?.ownershipRegionId,
|
|
1285
|
+
ownershipRegionKey: symbol?.metadata?.ownershipRegionKey,
|
|
1286
|
+
ownershipRegionKind: symbol?.metadata?.ownershipRegionKind,
|
|
1287
|
+
precision: occurrence.span ? 'declaration' : 'unknown',
|
|
1288
|
+
metadata: {
|
|
1289
|
+
source: 'external-semantic-index'
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
function scipSymbolId(symbol, context, documentId) {
|
|
1296
|
+
if (!symbol) return undefined;
|
|
1297
|
+
const raw = String(symbol);
|
|
1298
|
+
if (raw.startsWith('symbol:')) return raw;
|
|
1299
|
+
const scope = /^local\b/i.test(raw) ? `${documentId ?? context.idPart}:` : '';
|
|
1300
|
+
return `symbol:scip:${idFragment(scope + raw)}`;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
function semanticDbSymbolId(symbol, context, documentId) {
|
|
1304
|
+
if (!symbol) return `symbol:semanticdb:${context.idPart}:unknown`;
|
|
1305
|
+
if (String(symbol).startsWith('symbol:')) return String(symbol);
|
|
1306
|
+
const scope = /^local\d+$/i.test(String(symbol)) ? `${documentId}:` : '';
|
|
1307
|
+
return `symbol:semanticdb:${idFragment(scope + symbol)}`;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
function nameFromExternalSymbol(symbol) {
|
|
1311
|
+
const value = String(symbol ?? 'symbol');
|
|
1312
|
+
const cleaned = value
|
|
1313
|
+
.replace(/^symbol:[^:]+:/, '')
|
|
1314
|
+
.replace(/[`'"]/g, '')
|
|
1315
|
+
.split(/[\/#.:() +]+/)
|
|
1316
|
+
.filter(Boolean)
|
|
1317
|
+
.at(-1);
|
|
1318
|
+
return cleaned || value;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
const externalSymbolKindByNumber = Object.freeze({
|
|
1322
|
+
1: 'array',
|
|
1323
|
+
2: 'assertion',
|
|
1324
|
+
3: 'associatedType',
|
|
1325
|
+
4: 'attribute',
|
|
1326
|
+
7: 'class',
|
|
1327
|
+
8: 'constant',
|
|
1328
|
+
9: 'constructor',
|
|
1329
|
+
11: 'enum',
|
|
1330
|
+
12: 'enumMember',
|
|
1331
|
+
13: 'event',
|
|
1332
|
+
15: 'field',
|
|
1333
|
+
16: 'file',
|
|
1334
|
+
17: 'function',
|
|
1335
|
+
21: 'interface',
|
|
1336
|
+
25: 'macro',
|
|
1337
|
+
26: 'method',
|
|
1338
|
+
28: 'message',
|
|
1339
|
+
29: 'module',
|
|
1340
|
+
30: 'namespace',
|
|
1341
|
+
35: 'package',
|
|
1342
|
+
37: 'parameter',
|
|
1343
|
+
41: 'property',
|
|
1344
|
+
42: 'protocol',
|
|
1345
|
+
49: 'struct',
|
|
1346
|
+
53: 'trait',
|
|
1347
|
+
54: 'type',
|
|
1348
|
+
55: 'typeAlias',
|
|
1349
|
+
58: 'typeParameter',
|
|
1350
|
+
61: 'variable',
|
|
1351
|
+
66: 'abstractMethod'
|
|
1352
|
+
});
|
|
1353
|
+
|
|
1354
|
+
const lspSymbolKindByNumber = Object.freeze({
|
|
1355
|
+
1: 'file',
|
|
1356
|
+
2: 'module',
|
|
1357
|
+
3: 'namespace',
|
|
1358
|
+
4: 'package',
|
|
1359
|
+
5: 'class',
|
|
1360
|
+
6: 'method',
|
|
1361
|
+
7: 'property',
|
|
1362
|
+
8: 'field',
|
|
1363
|
+
9: 'constructor',
|
|
1364
|
+
10: 'enum',
|
|
1365
|
+
11: 'interface',
|
|
1366
|
+
12: 'function',
|
|
1367
|
+
13: 'variable',
|
|
1368
|
+
14: 'constant',
|
|
1369
|
+
22: 'enumMember',
|
|
1370
|
+
23: 'struct',
|
|
1371
|
+
26: 'typeParameter'
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
const externalLanguageNameByNumber = Object.freeze({
|
|
1375
|
+
1: 'csharp',
|
|
1376
|
+
2: 'swift',
|
|
1377
|
+
3: 'dart',
|
|
1378
|
+
4: 'kotlin',
|
|
1379
|
+
5: 'scala',
|
|
1380
|
+
6: 'java',
|
|
1381
|
+
15: 'python',
|
|
1382
|
+
16: 'ruby',
|
|
1383
|
+
17: 'elixir',
|
|
1384
|
+
18: 'erlang',
|
|
1385
|
+
19: 'php',
|
|
1386
|
+
22: 'javascript',
|
|
1387
|
+
23: 'typescript',
|
|
1388
|
+
33: 'go',
|
|
1389
|
+
34: 'c',
|
|
1390
|
+
35: 'cpp',
|
|
1391
|
+
38: 'zig',
|
|
1392
|
+
40: 'rust',
|
|
1393
|
+
44: 'haskell',
|
|
1394
|
+
54: 'r',
|
|
1395
|
+
69: 'sql'
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
function normalizeExternalSymbolKind(kind) {
|
|
1399
|
+
if (kind === undefined || kind === null || kind === '') return 'symbol';
|
|
1400
|
+
if (typeof kind === 'number') return externalSymbolKindByNumber[kind] ?? lspSymbolKindByNumber[kind] ?? `kind${kind}`;
|
|
1401
|
+
return String(kind).replace(/^[A-Z_]+_/, '').replace(/^[A-Z]/, (letter) => letter.toLowerCase());
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function normalizeLspSymbolKind(kind) {
|
|
1405
|
+
if (typeof kind === 'number') return lspSymbolKindByNumber[kind] ?? `kind${kind}`;
|
|
1406
|
+
return normalizeExternalSymbolKind(kind);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
function scipSyntaxKind(kind) {
|
|
1410
|
+
const normalized = typeof kind === 'number' ? kind : Number(kind);
|
|
1411
|
+
if (normalized === 15 || normalized === 16) return 'function';
|
|
1412
|
+
if (normalized === 19 || normalized === 20) return 'type';
|
|
1413
|
+
if (normalized === 25 || normalized === 26) return 'module';
|
|
1414
|
+
if (normalized === 9 || normalized === 10 || normalized === 12) return 'variable';
|
|
1415
|
+
return 'symbol';
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
function normalizeExternalOccurrenceRole(role) {
|
|
1419
|
+
const value = String(role ?? 'reference').toLowerCase();
|
|
1420
|
+
if (value.includes('def')) return 'definition';
|
|
1421
|
+
if (value.includes('decl')) return 'declaration';
|
|
1422
|
+
if (value.includes('import')) return 'import';
|
|
1423
|
+
if (value.includes('write')) return 'write';
|
|
1424
|
+
if (value.includes('read')) return 'read';
|
|
1425
|
+
return value === '2' ? 'definition' : value === '1' ? 'reference' : 'reference';
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
function scipOccurrenceRole(value) {
|
|
1429
|
+
const role = Number(value ?? 0);
|
|
1430
|
+
if ((role & 0x1) > 0) return 'definition';
|
|
1431
|
+
if ((role & 0x2) > 0) return 'import';
|
|
1432
|
+
if ((role & 0x4) > 0) return 'write';
|
|
1433
|
+
if ((role & 0x8) > 0) return 'read';
|
|
1434
|
+
return 'reference';
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
function scipOccurrenceRoleSet(value) {
|
|
1438
|
+
const role = Number(value ?? 0);
|
|
1439
|
+
const roles = [];
|
|
1440
|
+
if ((role & 0x1) > 0) roles.push('definition');
|
|
1441
|
+
if ((role & 0x2) > 0) roles.push('import');
|
|
1442
|
+
if ((role & 0x4) > 0) roles.push('write');
|
|
1443
|
+
if ((role & 0x8) > 0) roles.push('read');
|
|
1444
|
+
if ((role & 0x10) > 0) roles.push('generated');
|
|
1445
|
+
if ((role & 0x20) > 0) roles.push('test');
|
|
1446
|
+
if ((role & 0x40) > 0) roles.push('forwardDefinition');
|
|
1447
|
+
return roles.length ? roles : ['reference'];
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
function semanticDbOccurrenceRole(value) {
|
|
1451
|
+
const role = String(value ?? 'reference').toLowerCase();
|
|
1452
|
+
if (role === '2' || role.includes('definition')) return 'definition';
|
|
1453
|
+
return 'reference';
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
function scipRelationshipRelations(symbolInfo, symbolId, context) {
|
|
1457
|
+
return normalizeArray(symbolInfo.relationships).flatMap((relationship, index) => {
|
|
1458
|
+
const targetId = scipSymbolId(relationship.symbol, context);
|
|
1459
|
+
if (!targetId) return [];
|
|
1460
|
+
const predicates = [];
|
|
1461
|
+
if (relationship.is_reference ?? relationship.isReference) predicates.push('references');
|
|
1462
|
+
if (relationship.is_implementation ?? relationship.isImplementation) predicates.push('implements');
|
|
1463
|
+
if (relationship.is_type_definition ?? relationship.isTypeDefinition) predicates.push('typeDefinition');
|
|
1464
|
+
if (relationship.is_definition ?? relationship.isDefinition) predicates.push('definitionOf');
|
|
1465
|
+
return (predicates.length ? predicates : ['related']).map((predicate) => ({
|
|
1466
|
+
id: `rel_${idFragment(symbolId)}_${idFragment(targetId)}_${idFragment(predicate)}_${index + 1}`,
|
|
1467
|
+
sourceId: symbolId,
|
|
1468
|
+
predicate,
|
|
1469
|
+
targetId,
|
|
1470
|
+
metadata: { format: 'scip', relationship }
|
|
1471
|
+
}));
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
function scipSymbolFacts(symbolInfo, symbolId) {
|
|
1476
|
+
const facts = [];
|
|
1477
|
+
if (symbolInfo.documentation) {
|
|
1478
|
+
facts.push({
|
|
1479
|
+
id: `fact_${idFragment(symbolId)}_documentation`,
|
|
1480
|
+
predicate: 'documentation',
|
|
1481
|
+
subjectId: symbolId,
|
|
1482
|
+
value: normalizeArray(symbolInfo.documentation)
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
const signature = symbolInfo.signature_documentation ?? symbolInfo.signatureDocumentation;
|
|
1486
|
+
if (signature) {
|
|
1487
|
+
facts.push({
|
|
1488
|
+
id: `fact_${idFragment(symbolId)}_signature`,
|
|
1489
|
+
predicate: 'signature',
|
|
1490
|
+
subjectId: symbolId,
|
|
1491
|
+
value: signature
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
for (const [index, relationship] of normalizeArray(symbolInfo.relationships).entries()) {
|
|
1495
|
+
facts.push({
|
|
1496
|
+
id: `fact_${idFragment(symbolId)}_relationship_${index + 1}`,
|
|
1497
|
+
predicate: 'relationship',
|
|
1498
|
+
subjectId: symbolId,
|
|
1499
|
+
value: relationship
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
return facts;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
function semanticDbSymbolFacts(symbolInfo, symbolId) {
|
|
1506
|
+
const facts = [];
|
|
1507
|
+
for (const key of ['signature', 'properties', 'annotations', 'access', 'language']) {
|
|
1508
|
+
if (symbolInfo[key] !== undefined) {
|
|
1509
|
+
facts.push({
|
|
1510
|
+
id: `fact_${idFragment(symbolId)}_${idFragment(key)}`,
|
|
1511
|
+
predicate: key,
|
|
1512
|
+
subjectId: symbolId,
|
|
1513
|
+
value: symbolInfo[key]
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
return facts;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
function normalizeLspDocuments(payload, context) {
|
|
1521
|
+
if (Array.isArray(payload.documents)) return payload.documents;
|
|
1522
|
+
if (payload.textDocument || payload.uri || payload.documentSymbols || payload.symbols || payload.semanticTokens || payload.diagnostics) {
|
|
1523
|
+
return [{
|
|
1524
|
+
...payload,
|
|
1525
|
+
uri: payload.uri ?? payload.textDocument?.uri,
|
|
1526
|
+
languageId: payload.languageId ?? payload.language ?? context.language,
|
|
1527
|
+
documentSymbols: payload.documentSymbols,
|
|
1528
|
+
symbols: payload.symbols,
|
|
1529
|
+
semanticTokens: payload.semanticTokens,
|
|
1530
|
+
diagnostics: payload.diagnostics
|
|
1531
|
+
}];
|
|
1532
|
+
}
|
|
1533
|
+
return [{ uri: context.sourcePath, languageId: context.language }];
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
function addLspSymbol(result, symbol, input) {
|
|
1537
|
+
const location = symbol.location ?? {};
|
|
1538
|
+
const range = symbol.range ?? location.range ?? symbol.selectionRange;
|
|
1539
|
+
const sourcePath = uriToPath(location.uri ?? symbol.uri) ?? input.sourcePath;
|
|
1540
|
+
const symbolName = symbol.name ?? symbol.containerName ?? `symbol_${result.symbols.length + 1}`;
|
|
1541
|
+
const symbolId = symbol.id ?? `symbol:lsp:${idFragment(input.language ?? 'unknown')}:${idFragment([input.parentName, symbolName].filter(Boolean).join('.'))}`;
|
|
1542
|
+
const ownershipSpan = spanFromLspRange(range, sourcePath, input.context.sourceHash, 0);
|
|
1543
|
+
const selectionSpan = spanFromLspRange(symbol.selectionRange ?? range, sourcePath, input.context.sourceHash, 0);
|
|
1544
|
+
if (!result.symbols.some((entry) => entry.id === symbolId)) {
|
|
1545
|
+
result.symbols.push({
|
|
1546
|
+
id: symbolId,
|
|
1547
|
+
scheme: 'lsp',
|
|
1548
|
+
name: symbolName,
|
|
1549
|
+
kind: normalizeLspSymbolKind(symbol.kind),
|
|
1550
|
+
language: input.language,
|
|
1551
|
+
definitionSpan: ownershipSpan,
|
|
1552
|
+
signatureHash: hashSemanticValue([symbolName, symbol.kind, symbol.detail]),
|
|
1553
|
+
metadata: {
|
|
1554
|
+
format: 'lsp',
|
|
1555
|
+
detail: symbol.detail,
|
|
1556
|
+
tags: symbol.tags,
|
|
1557
|
+
deprecated: symbol.deprecated,
|
|
1558
|
+
containerName: symbol.containerName,
|
|
1559
|
+
parentName: input.parentName
|
|
1560
|
+
}
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
result.occurrences.push({
|
|
1564
|
+
id: `occ_${idFragment(symbolId)}_${result.occurrences.length + 1}`,
|
|
1565
|
+
documentId: input.documentId,
|
|
1566
|
+
symbolId,
|
|
1567
|
+
role: 'definition',
|
|
1568
|
+
span: selectionSpan,
|
|
1569
|
+
metadata: { format: 'lsp', range, selectionRange: symbol.selectionRange }
|
|
1570
|
+
});
|
|
1571
|
+
for (const child of normalizeArray(symbol.children)) {
|
|
1572
|
+
addLspSymbol(result, child, {
|
|
1573
|
+
...input,
|
|
1574
|
+
parentName: [input.parentName, symbolName].filter(Boolean).join('.')
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function addLspSemanticTokens(result, semanticTokens, input) {
|
|
1580
|
+
const data = normalizeArray(semanticTokens.data);
|
|
1581
|
+
const legend = semanticTokens.legend ?? {};
|
|
1582
|
+
let line = 0;
|
|
1583
|
+
let character = 0;
|
|
1584
|
+
for (let index = 0; index + 4 < data.length; index += 5) {
|
|
1585
|
+
line += Number(data[index] ?? 0);
|
|
1586
|
+
character = Number(data[index] ?? 0) === 0 ? character + Number(data[index + 1] ?? 0) : Number(data[index + 1] ?? 0);
|
|
1587
|
+
const length = Number(data[index + 2] ?? 0);
|
|
1588
|
+
const tokenType = legend.tokenTypes?.[Number(data[index + 3] ?? 0)] ?? `tokenType${data[index + 3] ?? 0}`;
|
|
1589
|
+
const span = {
|
|
1590
|
+
path: input.sourcePath,
|
|
1591
|
+
startLine: line + 1,
|
|
1592
|
+
startColumn: character + 1,
|
|
1593
|
+
endLine: line + 1,
|
|
1594
|
+
endColumn: character + length + 1
|
|
1595
|
+
};
|
|
1596
|
+
result.facts.push({
|
|
1597
|
+
id: `fact_${idFragment(input.documentId)}_semantic_token_${index / 5 + 1}`,
|
|
1598
|
+
predicate: 'semanticToken',
|
|
1599
|
+
subjectId: input.documentId,
|
|
1600
|
+
value: {
|
|
1601
|
+
tokenType,
|
|
1602
|
+
tokenModifiers: semanticTokenModifiers(Number(data[index + 4] ?? 0), legend.tokenModifiers),
|
|
1603
|
+
span
|
|
1604
|
+
},
|
|
1605
|
+
metadata: { format: 'lsp' }
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
function semanticTokenModifiers(bitset, modifiers = []) {
|
|
1611
|
+
const result = [];
|
|
1612
|
+
for (let index = 0; index < modifiers.length; index += 1) {
|
|
1613
|
+
if ((bitset & (1 << index)) > 0) result.push(modifiers[index]);
|
|
1614
|
+
}
|
|
1615
|
+
return result;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
function externalDiagnosticFact(diagnostic, context, documentId, sourcePath, index) {
|
|
1619
|
+
return {
|
|
1620
|
+
id: diagnostic.id ? `fact_${idFragment(diagnostic.id)}_diagnostic` : `fact_${context.idPart}_${idFragment(sourcePath)}_diagnostic_${index + 1}`,
|
|
1621
|
+
predicate: `${context.format}.diagnostic`,
|
|
1622
|
+
subjectId: documentId,
|
|
1623
|
+
value: {
|
|
1624
|
+
severity: diagnostic.severity,
|
|
1625
|
+
code: diagnostic.code,
|
|
1626
|
+
message: diagnostic.message,
|
|
1627
|
+
source: diagnostic.source,
|
|
1628
|
+
tags: diagnostic.tags,
|
|
1629
|
+
range: diagnostic.range,
|
|
1630
|
+
span: normalizeExternalSpan(diagnostic.range ?? diagnostic.span, sourcePath, context.sourceHash)
|
|
1631
|
+
},
|
|
1632
|
+
metadata: {
|
|
1633
|
+
format: context.format,
|
|
1634
|
+
source: 'external-semantic-index'
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function externalDiagnosticLoss(diagnostic, context, sourcePath) {
|
|
1640
|
+
const severity = externalDiagnosticSeverity(diagnostic.severity);
|
|
1641
|
+
return {
|
|
1642
|
+
id: diagnostic.id ?? `loss_${context.idPart}_${idFragment(diagnostic.code ?? diagnostic.message ?? severity)}_${idFragment(sourcePath)}`,
|
|
1643
|
+
severity,
|
|
1644
|
+
phase: 'index',
|
|
1645
|
+
sourceFormat: context.format,
|
|
1646
|
+
kind: severity === 'error' ? 'unsupportedSemantic' : 'partialSemanticIndex',
|
|
1647
|
+
message: String(diagnostic.message ?? `${context.format} diagnostic reported ${severity}.`),
|
|
1648
|
+
span: normalizeExternalSpan(diagnostic.range ?? diagnostic.span, sourcePath, context.sourceHash),
|
|
1649
|
+
metadata: {
|
|
1650
|
+
format: context.format,
|
|
1651
|
+
code: diagnostic.code,
|
|
1652
|
+
source: diagnostic.source,
|
|
1653
|
+
tags: diagnostic.tags
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
function externalDiagnosticSeverity(value) {
|
|
1659
|
+
if (value === undefined || value === null || value === '') return 'error';
|
|
1660
|
+
const raw = String(value).toLowerCase();
|
|
1661
|
+
if (raw === '1' || raw.includes('error')) return 'error';
|
|
1662
|
+
if (raw === '3' || raw.includes('info') || raw.includes('hint')) return 'info';
|
|
1663
|
+
return 'warning';
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
function spanFromScipOccurrence(occurrence, sourcePath, sourceHash) {
|
|
1667
|
+
if (occurrence.single_line_range || occurrence.singleLineRange) {
|
|
1668
|
+
const range = occurrence.single_line_range ?? occurrence.singleLineRange;
|
|
1669
|
+
return {
|
|
1670
|
+
sourceId: sourceHash,
|
|
1671
|
+
path: sourcePath,
|
|
1672
|
+
startLine: Number(range.line ?? 0) + 1,
|
|
1673
|
+
startColumn: Number(range.start_character ?? range.startCharacter ?? 0) + 1,
|
|
1674
|
+
endLine: Number(range.line ?? 0) + 1,
|
|
1675
|
+
endColumn: Number(range.end_character ?? range.endCharacter ?? 0) + 1
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
if (occurrence.multi_line_range || occurrence.multiLineRange) {
|
|
1679
|
+
const range = occurrence.multi_line_range ?? occurrence.multiLineRange;
|
|
1680
|
+
return {
|
|
1681
|
+
sourceId: sourceHash,
|
|
1682
|
+
path: sourcePath,
|
|
1683
|
+
startLine: Number(range.start_line ?? range.startLine ?? 0) + 1,
|
|
1684
|
+
startColumn: Number(range.start_character ?? range.startCharacter ?? 0) + 1,
|
|
1685
|
+
endLine: Number(range.end_line ?? range.endLine ?? 0) + 1,
|
|
1686
|
+
endColumn: Number(range.end_character ?? range.endCharacter ?? 0) + 1
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
const range = occurrence.range;
|
|
1690
|
+
if (Array.isArray(range) && range.length >= 3) {
|
|
1691
|
+
const startLine = Number(range[0] ?? 0);
|
|
1692
|
+
const startColumn = Number(range[1] ?? 0);
|
|
1693
|
+
const endLine = range.length >= 4 ? Number(range[2] ?? startLine) : startLine;
|
|
1694
|
+
const endColumn = range.length >= 4 ? Number(range[3] ?? startColumn) : Number(range[2] ?? startColumn);
|
|
1695
|
+
return {
|
|
1696
|
+
sourceId: sourceHash,
|
|
1697
|
+
path: sourcePath,
|
|
1698
|
+
startLine: startLine + 1,
|
|
1699
|
+
startColumn: startColumn + 1,
|
|
1700
|
+
endLine: endLine + 1,
|
|
1701
|
+
endColumn: endColumn + 1
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
return undefined;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
function spanFromSemanticDbRange(range, sourcePath, sourceHash) {
|
|
1708
|
+
if (!range) return undefined;
|
|
1709
|
+
return {
|
|
1710
|
+
sourceId: sourceHash,
|
|
1711
|
+
path: sourcePath,
|
|
1712
|
+
startLine: Number(range.start_line ?? range.startLine ?? 0) + 1,
|
|
1713
|
+
startColumn: Number(range.start_character ?? range.startCharacter ?? 0) + 1,
|
|
1714
|
+
endLine: Number(range.end_line ?? range.endLine ?? 0) + 1,
|
|
1715
|
+
endColumn: Number(range.end_character ?? range.endCharacter ?? 0) + 1
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
function spanFromLspRange(range, sourcePath, sourceHash, base = 0) {
|
|
1720
|
+
if (!range) return undefined;
|
|
1721
|
+
const source = range.start && range.end ? range : { start: range, end: range.end ?? range };
|
|
1722
|
+
return {
|
|
1723
|
+
sourceId: sourceHash,
|
|
1724
|
+
path: sourcePath,
|
|
1725
|
+
startLine: Number(source.start?.line ?? source.startLine ?? 0) + (base === 0 ? 1 : 0),
|
|
1726
|
+
startColumn: Number(source.start?.character ?? source.startColumn ?? 0) + (base === 0 ? 1 : 0),
|
|
1727
|
+
endLine: Number(source.end?.line ?? source.endLine ?? source.start?.line ?? 0) + (base === 0 ? 1 : 0),
|
|
1728
|
+
endColumn: Number(source.end?.character ?? source.endColumn ?? source.start?.character ?? 0) + (base === 0 ? 1 : 0)
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
function normalizeExternalSpan(value, sourcePath, sourceHash) {
|
|
1733
|
+
if (!value) return undefined;
|
|
1734
|
+
if (Array.isArray(value)) {
|
|
1735
|
+
return spanFromScipOccurrence({ range: value }, sourcePath, sourceHash);
|
|
1736
|
+
}
|
|
1737
|
+
if (value.start || value.end || value.line !== undefined) return spanFromLspRange(value, sourcePath, sourceHash, 0);
|
|
1738
|
+
if (value.startLine !== undefined || value.start_line !== undefined) {
|
|
1739
|
+
return {
|
|
1740
|
+
sourceId: value.sourceId ?? sourceHash,
|
|
1741
|
+
path: value.path ?? sourcePath,
|
|
1742
|
+
start: value.start,
|
|
1743
|
+
end: value.end,
|
|
1744
|
+
startLine: Number(value.startLine ?? value.start_line),
|
|
1745
|
+
startColumn: value.startColumn ?? value.start_character,
|
|
1746
|
+
endLine: value.endLine ?? value.end_line,
|
|
1747
|
+
endColumn: value.endColumn ?? value.end_character
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
return undefined;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
function uriToPath(uri) {
|
|
1754
|
+
if (typeof uri !== 'string') return undefined;
|
|
1755
|
+
if (uri.startsWith('file://')) {
|
|
1756
|
+
try {
|
|
1757
|
+
return decodeURIComponent(new URL(uri).pathname);
|
|
1758
|
+
} catch {
|
|
1759
|
+
return uri.replace(/^file:\/\//, '');
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
return uri;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
429
1765
|
export function projectFrontierAst(document, target = 'typescript', options = {}) {
|
|
430
1766
|
const normalized = normalizeCompileTarget(target);
|
|
431
1767
|
const projector = projectors[normalized];
|
|
@@ -473,6 +1809,70 @@ export function resolveCapabilityAdapters(document, target = 'typescript', optio
|
|
|
473
1809
|
});
|
|
474
1810
|
}
|
|
475
1811
|
|
|
1812
|
+
export function getNativeImportFeatureEvidencePolicy(kind) {
|
|
1813
|
+
const normalized = normalizeNativeLossKind({ kind }, 'warning');
|
|
1814
|
+
return NativeImportFeatureEvidencePolicies[normalized] ?? NativeImportFeatureEvidencePolicies[String(kind ?? '')];
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
export function summarizeNativeImportFeatureEvidence(losses = [], options = {}) {
|
|
1818
|
+
const normalizedLosses = normalizeNativeLossRecords(losses);
|
|
1819
|
+
const evidence = options.evidence ?? [];
|
|
1820
|
+
const issues = [];
|
|
1821
|
+
const byKind = {};
|
|
1822
|
+
const byRisk = {};
|
|
1823
|
+
const policyKinds = [];
|
|
1824
|
+
let highestRisk = 'low';
|
|
1825
|
+
let semanticMergeReadiness = 'ready';
|
|
1826
|
+
|
|
1827
|
+
for (const loss of normalizedLosses) {
|
|
1828
|
+
const policy = getNativeImportFeatureEvidencePolicy(loss.kind);
|
|
1829
|
+
if (!policy) continue;
|
|
1830
|
+
byKind[policy.kind] = (byKind[policy.kind] ?? 0) + 1;
|
|
1831
|
+
byRisk[policy.risk] = (byRisk[policy.risk] ?? 0) + 1;
|
|
1832
|
+
if ((nativeFeatureEvidenceRiskRank[policy.risk] ?? 0) > (nativeFeatureEvidenceRiskRank[highestRisk] ?? 0)) {
|
|
1833
|
+
highestRisk = policy.risk;
|
|
1834
|
+
}
|
|
1835
|
+
policyKinds.push(policy.kind);
|
|
1836
|
+
semanticMergeReadiness = maxSemanticMergeReadiness(semanticMergeReadiness, policy.minimumReadiness);
|
|
1837
|
+
const missingRequiredEvidence = policy.requiredEvidenceKeys.filter((key) => !nativeImportFeatureEvidenceHasKey(loss, evidence, key));
|
|
1838
|
+
const presentRequiredEvidence = policy.requiredEvidenceKeys.filter((key) => !missingRequiredEvidence.includes(key));
|
|
1839
|
+
const presentRecommendedEvidence = policy.recommendedEvidenceKeys.filter((key) => nativeImportFeatureEvidenceHasKey(loss, evidence, key));
|
|
1840
|
+
if (missingRequiredEvidence.length) {
|
|
1841
|
+
semanticMergeReadiness = maxSemanticMergeReadiness(semanticMergeReadiness, policy.missingEvidenceReadiness);
|
|
1842
|
+
}
|
|
1843
|
+
issues.push({
|
|
1844
|
+
lossId: loss.id,
|
|
1845
|
+
kind: loss.kind,
|
|
1846
|
+
policyKind: policy.kind,
|
|
1847
|
+
risk: policy.risk,
|
|
1848
|
+
category: policy.category,
|
|
1849
|
+
readiness: missingRequiredEvidence.length ? policy.missingEvidenceReadiness : policy.minimumReadiness,
|
|
1850
|
+
missingRequiredEvidence,
|
|
1851
|
+
presentRequiredEvidence,
|
|
1852
|
+
presentRecommendedEvidence,
|
|
1853
|
+
evidenceIds: nativeImportFeatureEvidenceIds(loss, evidence, policy)
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
const missingRequiredEvidence = issues.flatMap((issue) => issue.missingRequiredEvidence.map((key) => ({
|
|
1858
|
+
lossId: issue.lossId,
|
|
1859
|
+
kind: issue.kind,
|
|
1860
|
+
policyKind: issue.policyKind,
|
|
1861
|
+
evidenceKey: key
|
|
1862
|
+
})));
|
|
1863
|
+
return {
|
|
1864
|
+
total: issues.length,
|
|
1865
|
+
policyKinds: uniqueStrings(policyKinds),
|
|
1866
|
+
byKind,
|
|
1867
|
+
byRisk,
|
|
1868
|
+
highestRisk: issues.length ? highestRisk : 'low',
|
|
1869
|
+
semanticMergeReadiness,
|
|
1870
|
+
missingRequiredEvidence,
|
|
1871
|
+
issues,
|
|
1872
|
+
reasons: nativeImportFeatureEvidenceReasons(issues)
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
|
|
476
1876
|
export function summarizeNativeImportLosses(losses = [], options = {}) {
|
|
477
1877
|
const normalizedLosses = normalizeNativeLossRecords(losses);
|
|
478
1878
|
const bySeverity = { info: 0, warning: 0, error: 0 };
|
|
@@ -512,6 +1912,9 @@ export function summarizeNativeImportLosses(losses = [], options = {}) {
|
|
|
512
1912
|
reviewLossIds,
|
|
513
1913
|
informationalLossIds
|
|
514
1914
|
});
|
|
1915
|
+
const featureEvidence = summarizeNativeImportFeatureEvidence(normalizedLosses, {
|
|
1916
|
+
evidence: options.evidence
|
|
1917
|
+
});
|
|
515
1918
|
|
|
516
1919
|
return {
|
|
517
1920
|
total: normalizedLosses.length,
|
|
@@ -527,6 +1930,7 @@ export function summarizeNativeImportLosses(losses = [], options = {}) {
|
|
|
527
1930
|
reviewLossIds,
|
|
528
1931
|
informationalLossIds,
|
|
529
1932
|
failedEvidenceIds,
|
|
1933
|
+
featureEvidence,
|
|
530
1934
|
parser: options.parser,
|
|
531
1935
|
scanKind: options.scanKind,
|
|
532
1936
|
semanticStatus: options.semanticStatus
|
|
@@ -4112,6 +5516,61 @@ function nativeImportCategoryForLossKind(kind) {
|
|
|
4112
5516
|
return String(kind ?? 'opaqueNative');
|
|
4113
5517
|
}
|
|
4114
5518
|
|
|
5519
|
+
function nativeImportFeatureEvidencePolicy(kind, input = {}) {
|
|
5520
|
+
return Object.freeze({
|
|
5521
|
+
kind,
|
|
5522
|
+
category: input.category ?? nativeImportCategoryForLossKind(kind),
|
|
5523
|
+
risk: input.risk ?? 'medium',
|
|
5524
|
+
minimumReadiness: normalizeSemanticMergeReadiness(input.minimumReadiness) ?? 'needs-review',
|
|
5525
|
+
missingEvidenceReadiness: normalizeSemanticMergeReadiness(input.missingEvidenceReadiness) ?? 'needs-review',
|
|
5526
|
+
requiredEvidenceKeys: Object.freeze(uniqueStrings(input.requiredEvidenceKeys ?? [])),
|
|
5527
|
+
recommendedEvidenceKeys: Object.freeze(uniqueStrings(input.recommendedEvidenceKeys ?? [])),
|
|
5528
|
+
notes: Object.freeze(uniqueStrings(input.notes ?? []))
|
|
5529
|
+
});
|
|
5530
|
+
}
|
|
5531
|
+
|
|
5532
|
+
function nativeImportFeatureEvidenceHasKey(loss, evidence, key) {
|
|
5533
|
+
return nativeImportFeatureEvidenceValuePresent(nativeImportFeatureEvidenceValue(loss, key))
|
|
5534
|
+
|| (evidence ?? []).some((record) => nativeImportFeatureEvidenceValuePresent(nativeImportFeatureEvidenceValue(record, key)));
|
|
5535
|
+
}
|
|
5536
|
+
|
|
5537
|
+
function nativeImportFeatureEvidenceValue(record, key) {
|
|
5538
|
+
if (!record || !key) return undefined;
|
|
5539
|
+
const candidates = [record, record.metadata].filter(Boolean);
|
|
5540
|
+
for (const candidate of candidates) {
|
|
5541
|
+
const direct = candidate[key];
|
|
5542
|
+
if (direct !== undefined) return direct;
|
|
5543
|
+
const dotted = String(key).split('.').reduce((current, part) => current?.[part], candidate);
|
|
5544
|
+
if (dotted !== undefined) return dotted;
|
|
5545
|
+
}
|
|
5546
|
+
return undefined;
|
|
5547
|
+
}
|
|
5548
|
+
|
|
5549
|
+
function nativeImportFeatureEvidenceValuePresent(value) {
|
|
5550
|
+
if (value === undefined || value === null) return false;
|
|
5551
|
+
if (typeof value === 'string') return value.trim().length > 0;
|
|
5552
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
5553
|
+
if (typeof value === 'object') return Object.keys(value).length > 0;
|
|
5554
|
+
return true;
|
|
5555
|
+
}
|
|
5556
|
+
|
|
5557
|
+
function nativeImportFeatureEvidenceIds(loss, evidence, policy) {
|
|
5558
|
+
const keys = [...policy.requiredEvidenceKeys, ...policy.recommendedEvidenceKeys];
|
|
5559
|
+
return uniqueStrings((evidence ?? [])
|
|
5560
|
+
.filter((record) => keys.some((key) => nativeImportFeatureEvidenceValuePresent(nativeImportFeatureEvidenceValue(record, key))))
|
|
5561
|
+
.map((record) => record.id)
|
|
5562
|
+
.filter(Boolean)
|
|
5563
|
+
.concat(loss.evidenceIds ?? []));
|
|
5564
|
+
}
|
|
5565
|
+
|
|
5566
|
+
function nativeImportFeatureEvidenceReasons(issues) {
|
|
5567
|
+
return uniqueStrings((issues ?? []).flatMap((issue) => {
|
|
5568
|
+
const missing = issue.missingRequiredEvidence ?? [];
|
|
5569
|
+
if (!missing.length) return [];
|
|
5570
|
+
return [`${issue.kind} loss ${issue.lossId} is missing required evidence: ${missing.join(', ')}.`];
|
|
5571
|
+
}));
|
|
5572
|
+
}
|
|
5573
|
+
|
|
4115
5574
|
function normalizeProjectionMatrixTargets(targets) {
|
|
4116
5575
|
return uniqueStrings((Array.isArray(targets) ? targets : [targets])
|
|
4117
5576
|
.map((target) => {
|