@shapeshift-labs/frontier-lang-compiler 0.2.6 → 0.2.8
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 +50 -0
- package/bench/smoke.mjs +45 -2
- package/dist/index.d.ts +178 -1
- package/dist/index.js +2266 -121
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
createNativeAstRecord,
|
|
5
5
|
createPatch,
|
|
6
6
|
createSemanticIndexRecord,
|
|
7
|
+
createSourceMapRecord,
|
|
7
8
|
createUniversalAstEnvelope,
|
|
8
9
|
hashDocumentBase,
|
|
9
10
|
hashSemanticValue,
|
|
10
11
|
nativeSourceNode,
|
|
11
12
|
stableUniversalAstJson,
|
|
13
|
+
validateSourceMapRecord,
|
|
12
14
|
validateUniversalAstEnvelope
|
|
13
15
|
} from '@shapeshift-labs/frontier-lang-kernel';
|
|
14
16
|
import { parseFrontierFile, parseFrontierSource } from '@shapeshift-labs/frontier-lang-parser';
|
|
@@ -51,6 +53,56 @@ const canonicalTargets = Object.freeze({
|
|
|
51
53
|
h: 'c'
|
|
52
54
|
});
|
|
53
55
|
|
|
56
|
+
const lossSeverityRank = Object.freeze({
|
|
57
|
+
none: 0,
|
|
58
|
+
info: 1,
|
|
59
|
+
warning: 2,
|
|
60
|
+
error: 3
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const semanticMergeReadinessRank = Object.freeze({
|
|
64
|
+
ready: 0,
|
|
65
|
+
'ready-with-losses': 1,
|
|
66
|
+
'needs-review': 2,
|
|
67
|
+
blocked: 3
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export const NativeImportTaxonomyKinds = Object.freeze([
|
|
71
|
+
'exactAstImport',
|
|
72
|
+
'declarationsOnly',
|
|
73
|
+
'opaqueBodies',
|
|
74
|
+
'macroExpansion',
|
|
75
|
+
'preprocessor',
|
|
76
|
+
'metaprogramming',
|
|
77
|
+
'generatedCode',
|
|
78
|
+
'sourcePreservation',
|
|
79
|
+
'parserDiagnostics',
|
|
80
|
+
'unsupportedSyntax',
|
|
81
|
+
'partialSemanticIndex',
|
|
82
|
+
'sourceMapApproximation'
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
export const NativeImportLossKinds = Object.freeze([
|
|
86
|
+
'declarationOnlyCoverage',
|
|
87
|
+
'opaqueNative',
|
|
88
|
+
'macroExpansion',
|
|
89
|
+
'preprocessor',
|
|
90
|
+
'metaprogramming',
|
|
91
|
+
'generatedCode',
|
|
92
|
+
'sourcePreservation',
|
|
93
|
+
'parserDiagnostic',
|
|
94
|
+
'unsupportedSyntax',
|
|
95
|
+
'partialSemanticIndex',
|
|
96
|
+
'sourceMapApproximation'
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
export const NativeImportReadinessBySeverity = Object.freeze({
|
|
100
|
+
none: 'ready',
|
|
101
|
+
info: 'ready-with-losses',
|
|
102
|
+
warning: 'needs-review',
|
|
103
|
+
error: 'blocked'
|
|
104
|
+
});
|
|
105
|
+
|
|
54
106
|
export function normalizeCompileTarget(target) {
|
|
55
107
|
const normalized = String(target ?? 'typescript').toLowerCase();
|
|
56
108
|
const canonical = canonicalTargets[normalized] ?? normalized;
|
|
@@ -135,6 +187,187 @@ export function resolveCapabilityAdapters(document, target = 'typescript', optio
|
|
|
135
187
|
});
|
|
136
188
|
}
|
|
137
189
|
|
|
190
|
+
export function summarizeNativeImportLosses(losses = [], options = {}) {
|
|
191
|
+
const normalizedLosses = normalizeNativeLossRecords(losses);
|
|
192
|
+
const bySeverity = { info: 0, warning: 0, error: 0 };
|
|
193
|
+
const byKind = {};
|
|
194
|
+
const blockingLossIds = [];
|
|
195
|
+
const reviewLossIds = [];
|
|
196
|
+
const informationalLossIds = [];
|
|
197
|
+
let highestSeverity = 'none';
|
|
198
|
+
|
|
199
|
+
for (const loss of normalizedLosses) {
|
|
200
|
+
bySeverity[loss.severity] += 1;
|
|
201
|
+
byKind[loss.kind] = (byKind[loss.kind] ?? 0) + 1;
|
|
202
|
+
if (lossSeverityRank[loss.severity] > lossSeverityRank[highestSeverity]) {
|
|
203
|
+
highestSeverity = loss.severity;
|
|
204
|
+
}
|
|
205
|
+
if (loss.severity === 'error') blockingLossIds.push(loss.id);
|
|
206
|
+
else if (loss.severity === 'warning') reviewLossIds.push(loss.id);
|
|
207
|
+
else informationalLossIds.push(loss.id);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const failedEvidenceIds = (options.evidence ?? [])
|
|
211
|
+
.filter((record) => record?.status === 'failed')
|
|
212
|
+
.map((record) => record.id)
|
|
213
|
+
.filter(Boolean);
|
|
214
|
+
const exactAst = Boolean(options.exactAst) && normalizedLosses.length === 0;
|
|
215
|
+
const categories = uniqueStrings([
|
|
216
|
+
...(exactAst ? ['exactAstImport'] : []),
|
|
217
|
+
...normalizedLosses.map((loss) => nativeImportCategoryForLossKind(loss.kind))
|
|
218
|
+
]);
|
|
219
|
+
const semanticMergeReadiness = failedEvidenceIds.length
|
|
220
|
+
? 'blocked'
|
|
221
|
+
: NativeImportReadinessBySeverity[highestSeverity];
|
|
222
|
+
const readinessReasons = nativeImportReadinessReasons({
|
|
223
|
+
exactAst,
|
|
224
|
+
failedEvidenceIds,
|
|
225
|
+
blockingLossIds,
|
|
226
|
+
reviewLossIds,
|
|
227
|
+
informationalLossIds
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
total: normalizedLosses.length,
|
|
232
|
+
hasLosses: normalizedLosses.length > 0,
|
|
233
|
+
exactAst,
|
|
234
|
+
highestSeverity,
|
|
235
|
+
semanticMergeReadiness,
|
|
236
|
+
readinessReasons,
|
|
237
|
+
categories,
|
|
238
|
+
bySeverity,
|
|
239
|
+
byKind,
|
|
240
|
+
blockingLossIds,
|
|
241
|
+
reviewLossIds,
|
|
242
|
+
informationalLossIds,
|
|
243
|
+
failedEvidenceIds,
|
|
244
|
+
parser: options.parser,
|
|
245
|
+
scanKind: options.scanKind,
|
|
246
|
+
semanticStatus: options.semanticStatus
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function classifyNativeImportReadiness(losses = [], options = {}) {
|
|
251
|
+
const summary = summarizeNativeImportLosses(losses, options);
|
|
252
|
+
return {
|
|
253
|
+
readiness: summary.semanticMergeReadiness,
|
|
254
|
+
reasons: summary.readinessReasons,
|
|
255
|
+
summary
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function createEstreeNativeImporterAdapter(options = {}) {
|
|
260
|
+
return createJavaScriptSyntaxImporterAdapter({
|
|
261
|
+
id: 'frontier.estree-native-importer',
|
|
262
|
+
language: 'javascript',
|
|
263
|
+
parser: 'estree',
|
|
264
|
+
supportedExtensions: ['.js', '.mjs', '.cjs', '.jsx'],
|
|
265
|
+
astFormat: 'estree',
|
|
266
|
+
...options
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function createBabelNativeImporterAdapter(options = {}) {
|
|
271
|
+
return createJavaScriptSyntaxImporterAdapter({
|
|
272
|
+
id: 'frontier.babel-native-importer',
|
|
273
|
+
language: 'javascript',
|
|
274
|
+
parser: 'babel',
|
|
275
|
+
supportedExtensions: ['.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx'],
|
|
276
|
+
astFormat: 'babel',
|
|
277
|
+
defaultParserOptions: {
|
|
278
|
+
errorRecovery: true,
|
|
279
|
+
ranges: true,
|
|
280
|
+
sourceType: 'unambiguous',
|
|
281
|
+
plugins: ['typescript', 'jsx']
|
|
282
|
+
},
|
|
283
|
+
...options
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function createTypeScriptCompilerNativeImporterAdapter(options = {}) {
|
|
288
|
+
return {
|
|
289
|
+
id: options.id ?? 'frontier.typescript-compiler-native-importer',
|
|
290
|
+
language: options.language ?? 'typescript',
|
|
291
|
+
parser: options.parser ?? 'typescript-compiler-api',
|
|
292
|
+
version: options.version,
|
|
293
|
+
capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
|
|
294
|
+
supportedExtensions: options.supportedExtensions ?? ['.ts', '.tsx', '.js', '.jsx'],
|
|
295
|
+
diagnostics: options.diagnostics,
|
|
296
|
+
parse(input) {
|
|
297
|
+
const ts = options.typescript ?? options.ts ?? input.options?.typescript ?? input.options?.ts;
|
|
298
|
+
const sourceFile = input.options?.sourceFile ?? input.options?.ast ?? options.sourceFile ?? createTypeScriptSourceFile(ts, input, options);
|
|
299
|
+
if (!sourceFile) {
|
|
300
|
+
return missingInjectedParserResult(input, {
|
|
301
|
+
parser: options.parser ?? 'typescript-compiler-api',
|
|
302
|
+
adapterId: options.id ?? 'frontier.typescript-compiler-native-importer',
|
|
303
|
+
message: 'createTypeScriptCompilerNativeImporterAdapter requires an injected TypeScript module, createSourceFile function, sourceFile, or adapterOptions.sourceFile.'
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return createNativeImportFromTypeScriptAst(sourceFile, input, {
|
|
307
|
+
parser: options.parser ?? 'typescript-compiler-api',
|
|
308
|
+
astFormat: 'typescript-compiler-api',
|
|
309
|
+
ts,
|
|
310
|
+
maxNodes: options.maxNodes,
|
|
311
|
+
includeTokens: options.includeTokens
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function createTreeSitterNativeImporterAdapter(options = {}) {
|
|
318
|
+
return {
|
|
319
|
+
id: options.id ?? `frontier.tree-sitter-${idFragment(options.language ?? 'source')}-native-importer`,
|
|
320
|
+
language: options.language ?? 'source',
|
|
321
|
+
parser: options.parserName ?? options.parser ?? 'tree-sitter',
|
|
322
|
+
version: options.version,
|
|
323
|
+
capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
|
|
324
|
+
supportedExtensions: options.supportedExtensions ?? [],
|
|
325
|
+
diagnostics: options.diagnostics,
|
|
326
|
+
parse(input) {
|
|
327
|
+
const tree = input.options?.tree ?? options.tree ?? parseTreeSitterSource(input, options);
|
|
328
|
+
const root = tree?.rootNode ?? tree;
|
|
329
|
+
if (!root) {
|
|
330
|
+
return missingInjectedParserResult(input, {
|
|
331
|
+
parser: options.parserName ?? options.parser ?? 'tree-sitter',
|
|
332
|
+
adapterId: options.id ?? `frontier.tree-sitter-${idFragment(options.language ?? input.language)}-native-importer`,
|
|
333
|
+
message: 'createTreeSitterNativeImporterAdapter requires an injected tree-sitter parser/tree or adapterOptions.tree.'
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return createNativeImportFromTreeSitter(root, input, {
|
|
337
|
+
parser: options.parserName ?? options.parser ?? 'tree-sitter',
|
|
338
|
+
astFormat: 'tree-sitter',
|
|
339
|
+
maxNodes: options.maxNodes
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export async function importNativeProject(input = {}) {
|
|
346
|
+
const sources = input.sources ?? [];
|
|
347
|
+
const adapters = input.adapters ?? [];
|
|
348
|
+
const imports = [];
|
|
349
|
+
for (const [index, source] of sources.entries()) {
|
|
350
|
+
const adapter = source.adapter && typeof source.adapter === 'object'
|
|
351
|
+
? source.adapter
|
|
352
|
+
: resolveNativeProjectAdapter(source, adapters, input);
|
|
353
|
+
if (adapter) {
|
|
354
|
+
imports.push(await runNativeImporterAdapter(adapter, {
|
|
355
|
+
...source,
|
|
356
|
+
adapterOptions: source.adapterOptions ?? input.adapterOptions,
|
|
357
|
+
adapterMetadata: {
|
|
358
|
+
projectImportId: input.id,
|
|
359
|
+
sourceIndex: index,
|
|
360
|
+
...input.adapterMetadata,
|
|
361
|
+
...source.adapterMetadata
|
|
362
|
+
}
|
|
363
|
+
}));
|
|
364
|
+
} else {
|
|
365
|
+
imports.push(importNativeSource(source));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return createNativeProjectImportResult(input, imports);
|
|
369
|
+
}
|
|
370
|
+
|
|
138
371
|
export async function runNativeImporterAdapter(adapter, input = {}) {
|
|
139
372
|
const summary = normalizeNativeImporterAdapter(adapter);
|
|
140
373
|
const language = input.language ?? summary.language;
|
|
@@ -259,6 +492,8 @@ export function importNativeSource(input) {
|
|
|
259
492
|
if (!language) throw new Error('importNativeSource requires a language or nativeAst.language');
|
|
260
493
|
const sourcePath = input.sourcePath ?? input.nativeAst?.sourcePath;
|
|
261
494
|
const sourceHash = input.sourceHash ?? input.nativeAst?.sourceHash ?? (input.sourceText ? hashSemanticValue(input.sourceText) : hashSemanticValue(input.nativeAst?.nodes ?? input.nativeAst ?? {}));
|
|
495
|
+
const targetPath = input.targetPath ?? input.target?.emitPath;
|
|
496
|
+
const targetHash = input.targetHash;
|
|
262
497
|
const importIdPart = idFragment(input.id ?? input.nativeSourceId ?? sourcePath ?? language);
|
|
263
498
|
const lightweight = !input.nativeAst && !input.nodes && input.sourceText
|
|
264
499
|
? createLightweightNativeImport({
|
|
@@ -294,7 +529,9 @@ export function importNativeSource(input) {
|
|
|
294
529
|
}
|
|
295
530
|
});
|
|
296
531
|
const frontierNodeIds = input.frontierNodeIds ?? input.semanticNodes?.map((node) => node.id) ?? [];
|
|
297
|
-
const
|
|
532
|
+
const semanticNodes = input.semanticNodes ?? [];
|
|
533
|
+
const semanticStatus = input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only');
|
|
534
|
+
const losses = normalizeNativeLossRecords(input.losses ?? nativeAst.losses ?? lightweight?.losses ?? []);
|
|
298
535
|
const nativeSource = nativeSourceNode({
|
|
299
536
|
id: input.nativeSourceId ?? `native_source_${importIdPart}`,
|
|
300
537
|
name: input.name ?? sourcePath?.split(/[\\/]/).filter(Boolean).at(-1) ?? `${language}NativeSource`,
|
|
@@ -309,12 +546,11 @@ export function importNativeSource(input) {
|
|
|
309
546
|
losses,
|
|
310
547
|
target: input.target,
|
|
311
548
|
metadata: {
|
|
312
|
-
semanticStatus
|
|
549
|
+
semanticStatus,
|
|
313
550
|
mappings: input.mappings ?? [],
|
|
314
551
|
...input.nativeSourceMetadata
|
|
315
552
|
}
|
|
316
553
|
});
|
|
317
|
-
const semanticNodes = input.semanticNodes ?? [];
|
|
318
554
|
const document = createDocument({
|
|
319
555
|
id: input.documentId ?? `document_${importIdPart}`,
|
|
320
556
|
name: input.documentName ?? nativeSource.name,
|
|
@@ -322,11 +558,11 @@ export function importNativeSource(input) {
|
|
|
322
558
|
rootIds: input.rootIds,
|
|
323
559
|
metadata: {
|
|
324
560
|
sourceLanguage: language,
|
|
325
|
-
semanticStatus
|
|
561
|
+
semanticStatus,
|
|
326
562
|
...input.documentMetadata
|
|
327
563
|
}
|
|
328
564
|
});
|
|
329
|
-
const
|
|
565
|
+
const baseEvidence = input.evidence ?? [{
|
|
330
566
|
id: input.evidenceId ?? `evidence_${importIdPart}_import`,
|
|
331
567
|
kind: 'import',
|
|
332
568
|
status: losses.some((loss) => loss.severity === 'error') ? 'failed' : 'passed',
|
|
@@ -335,21 +571,86 @@ export function importNativeSource(input) {
|
|
|
335
571
|
metadata: {
|
|
336
572
|
parser: nativeAst.parser,
|
|
337
573
|
sourcePath,
|
|
338
|
-
semanticStatus
|
|
574
|
+
semanticStatus
|
|
339
575
|
}
|
|
340
576
|
}];
|
|
577
|
+
const lossSummary = summarizeNativeImportLosses(losses, {
|
|
578
|
+
exactAst: Boolean(input.nativeAst || input.nodes),
|
|
579
|
+
evidence: baseEvidence,
|
|
580
|
+
parser: nativeAst.parser,
|
|
581
|
+
scanKind: lightweight?.metadata?.scanKind,
|
|
582
|
+
semanticStatus
|
|
583
|
+
});
|
|
584
|
+
const evidence = attachNativeImportLossSummary(baseEvidence, lossSummary);
|
|
341
585
|
const semanticIndex = input.semanticIndex ?? lightweight?.semanticIndex;
|
|
586
|
+
const sourceMapMappings = normalizeSourceMapMappings(
|
|
587
|
+
input.mappings ?? lightweight?.mappings ?? inferSourceMapMappings({
|
|
588
|
+
semanticIndex,
|
|
589
|
+
nativeAst,
|
|
590
|
+
nativeSource,
|
|
591
|
+
evidence
|
|
592
|
+
}),
|
|
593
|
+
{
|
|
594
|
+
semanticIndex,
|
|
595
|
+
nativeAst,
|
|
596
|
+
nativeSource,
|
|
597
|
+
evidence,
|
|
598
|
+
losses,
|
|
599
|
+
target: input.target,
|
|
600
|
+
targetPath,
|
|
601
|
+
targetHash
|
|
602
|
+
}
|
|
603
|
+
);
|
|
604
|
+
const inferredTargetPath = targetPath ?? commonGeneratedTargetPath(sourceMapMappings);
|
|
605
|
+
const inferredSourceMaps = sourceMapMappings.length
|
|
606
|
+
? [createSourceMapRecord({
|
|
607
|
+
id: input.sourceMapId ?? `source_map_${importIdPart}`,
|
|
608
|
+
sourcePath,
|
|
609
|
+
sourceHash,
|
|
610
|
+
target: input.target,
|
|
611
|
+
targetPath: inferredTargetPath,
|
|
612
|
+
targetHash,
|
|
613
|
+
semanticIndexId: semanticIndex?.id,
|
|
614
|
+
nativeAstId: nativeAst.id,
|
|
615
|
+
nativeSourceId: nativeSource.id,
|
|
616
|
+
mappings: sourceMapMappings,
|
|
617
|
+
evidence,
|
|
618
|
+
metadata: {
|
|
619
|
+
sourceLanguage: language,
|
|
620
|
+
parser: nativeAst.parser,
|
|
621
|
+
semanticStatus
|
|
622
|
+
}
|
|
623
|
+
})]
|
|
624
|
+
: [];
|
|
625
|
+
const sourceMaps = normalizeSourceMaps(input.sourceMaps ?? inferredSourceMaps, {
|
|
626
|
+
document,
|
|
627
|
+
nativeSources: [nativeSource],
|
|
628
|
+
nativeAst,
|
|
629
|
+
nativeSource,
|
|
630
|
+
semanticIndex,
|
|
631
|
+
evidence,
|
|
632
|
+
losses,
|
|
633
|
+
target: input.target,
|
|
634
|
+
targetPath: inferredTargetPath,
|
|
635
|
+
targetHash,
|
|
636
|
+
sourcePath,
|
|
637
|
+
sourceHash,
|
|
638
|
+
defaultSourceMapId: `source_map_${importIdPart}`
|
|
639
|
+
});
|
|
640
|
+
const resultSourceMapMappings = sourceMaps.flatMap((sourceMap) => sourceMap.mappings ?? []);
|
|
342
641
|
const universalAst = createUniversalAstEnvelope({
|
|
343
642
|
id: input.universalAstId ?? `universal_ast_${importIdPart}`,
|
|
344
643
|
document,
|
|
345
644
|
nativeSources: [nativeSource],
|
|
346
645
|
semanticIndex,
|
|
646
|
+
sourceMaps,
|
|
347
647
|
losses,
|
|
348
648
|
evidence,
|
|
349
649
|
metadata: {
|
|
350
650
|
sourceLanguage: language,
|
|
351
651
|
sourcePath,
|
|
352
|
-
semanticStatus
|
|
652
|
+
semanticStatus,
|
|
653
|
+
nativeImportLossSummary: lossSummary,
|
|
353
654
|
...input.universalAstMetadata
|
|
354
655
|
}
|
|
355
656
|
});
|
|
@@ -363,29 +664,40 @@ export function importNativeSource(input) {
|
|
|
363
664
|
touches: [{ id: node.id, access: node.kind === 'nativeSource' ? 'evidence' : 'schema' }]
|
|
364
665
|
})),
|
|
365
666
|
evidence,
|
|
366
|
-
metadata: {
|
|
667
|
+
metadata: {
|
|
668
|
+
sourceLanguage: language,
|
|
669
|
+
sourcePath,
|
|
670
|
+
semanticIndexId: semanticIndex?.id,
|
|
671
|
+
universalAstId: universalAst.id,
|
|
672
|
+
sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
|
|
673
|
+
nativeImportLossSummary: lossSummary
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
const importResult = createImportResult({
|
|
677
|
+
id: input.id ?? `import_${importIdPart}`,
|
|
678
|
+
language,
|
|
679
|
+
sourcePath,
|
|
680
|
+
document,
|
|
681
|
+
patch,
|
|
682
|
+
nativeAst,
|
|
683
|
+
semanticIndex,
|
|
684
|
+
universalAst,
|
|
685
|
+
sourceMaps,
|
|
686
|
+
losses,
|
|
687
|
+
evidence,
|
|
688
|
+
metadata: {
|
|
689
|
+
nativeSourceId: nativeSource.id,
|
|
690
|
+
semanticIndexId: semanticIndex?.id,
|
|
691
|
+
universalAstId: universalAst.id,
|
|
692
|
+
sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
|
|
693
|
+
semanticStatus,
|
|
694
|
+
mappings: resultSourceMapMappings,
|
|
695
|
+
nativeImportLossSummary: lossSummary,
|
|
696
|
+
...input.metadata
|
|
697
|
+
}
|
|
367
698
|
});
|
|
368
699
|
return {
|
|
369
|
-
...
|
|
370
|
-
id: input.id ?? `import_${importIdPart}`,
|
|
371
|
-
language,
|
|
372
|
-
sourcePath,
|
|
373
|
-
document,
|
|
374
|
-
patch,
|
|
375
|
-
nativeAst,
|
|
376
|
-
semanticIndex,
|
|
377
|
-
universalAst,
|
|
378
|
-
losses,
|
|
379
|
-
evidence,
|
|
380
|
-
metadata: {
|
|
381
|
-
nativeSourceId: nativeSource.id,
|
|
382
|
-
semanticIndexId: semanticIndex?.id,
|
|
383
|
-
universalAstId: universalAst.id,
|
|
384
|
-
semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only'),
|
|
385
|
-
mappings: input.mappings ?? [],
|
|
386
|
-
...input.metadata
|
|
387
|
-
}
|
|
388
|
-
}),
|
|
700
|
+
...withNativeImportReadiness(importResult, lossSummary),
|
|
389
701
|
nativeSource
|
|
390
702
|
};
|
|
391
703
|
}
|
|
@@ -409,6 +721,8 @@ function createLightweightNativeImport(input) {
|
|
|
409
721
|
const occurrences = [];
|
|
410
722
|
const relations = [];
|
|
411
723
|
const facts = [];
|
|
724
|
+
const mappings = [];
|
|
725
|
+
const evidenceId = `evidence_${idFragment(input.sourcePath ?? input.language)}_lightweight_scan`;
|
|
412
726
|
|
|
413
727
|
for (const declaration of declarations) {
|
|
414
728
|
nodes[rootId].children.push(declaration.nodeId);
|
|
@@ -422,6 +736,7 @@ function createLightweightNativeImport(input) {
|
|
|
422
736
|
metadata: declaration.metadata
|
|
423
737
|
};
|
|
424
738
|
if (declaration.symbolId) {
|
|
739
|
+
const occurrenceId = `occ_${idFragment(declaration.nodeId)}_def`;
|
|
425
740
|
symbols.push({
|
|
426
741
|
id: declaration.symbolId,
|
|
427
742
|
scheme: 'frontier',
|
|
@@ -433,7 +748,7 @@ function createLightweightNativeImport(input) {
|
|
|
433
748
|
definitionSpan: declaration.span
|
|
434
749
|
});
|
|
435
750
|
occurrences.push({
|
|
436
|
-
id:
|
|
751
|
+
id: occurrenceId,
|
|
437
752
|
documentId,
|
|
438
753
|
symbolId: declaration.symbolId,
|
|
439
754
|
role: declaration.role ?? 'definition',
|
|
@@ -452,9 +767,20 @@ function createLightweightNativeImport(input) {
|
|
|
452
767
|
subjectId: declaration.symbolId,
|
|
453
768
|
value: declaration.languageKind
|
|
454
769
|
});
|
|
770
|
+
mappings.push({
|
|
771
|
+
id: `map_${idFragment(declaration.nodeId)}`,
|
|
772
|
+
nativeAstNodeId: declaration.nodeId,
|
|
773
|
+
semanticSymbolId: declaration.symbolId,
|
|
774
|
+
semanticOccurrenceId: occurrenceId,
|
|
775
|
+
sourceSpan: declaration.span,
|
|
776
|
+
evidenceIds: [evidenceId],
|
|
777
|
+
lossIds: declaration.loss ? [declaration.loss.id] : [],
|
|
778
|
+
precision: 'declaration'
|
|
779
|
+
});
|
|
455
780
|
}
|
|
456
781
|
if (declaration.loss) losses.push(declaration.loss);
|
|
457
782
|
}
|
|
783
|
+
losses.push(...lightweightCoverageLosses(input, declarations));
|
|
458
784
|
|
|
459
785
|
const semanticIndex = createSemanticIndexRecord({
|
|
460
786
|
id: `index_${idFragment(input.sourcePath ?? input.language)}`,
|
|
@@ -469,7 +795,7 @@ function createLightweightNativeImport(input) {
|
|
|
469
795
|
relations,
|
|
470
796
|
facts,
|
|
471
797
|
evidence: [{
|
|
472
|
-
id:
|
|
798
|
+
id: evidenceId,
|
|
473
799
|
kind: 'import',
|
|
474
800
|
status: 'passed',
|
|
475
801
|
path: input.sourcePath,
|
|
@@ -489,6 +815,7 @@ function createLightweightNativeImport(input) {
|
|
|
489
815
|
nodes,
|
|
490
816
|
losses,
|
|
491
817
|
semanticIndex,
|
|
818
|
+
mappings,
|
|
492
819
|
metadata: { parser, scanKind: 'lightweight-declaration-scan', declarationCount: declarations.length }
|
|
493
820
|
};
|
|
494
821
|
}
|
|
@@ -499,6 +826,23 @@ function scanNativeDeclarations(input) {
|
|
|
499
826
|
if (language === 'python') return scanPython(input);
|
|
500
827
|
if (language === 'rust') return scanRust(input);
|
|
501
828
|
if (language === 'c' || language === 'cpp' || language === 'c++') return scanCLike(input);
|
|
829
|
+
if (language === 'java') return scanJava(input);
|
|
830
|
+
if (language === 'go') return scanGo(input);
|
|
831
|
+
if (language === 'swift') return scanSwift(input);
|
|
832
|
+
if (language === 'csharp' || language === 'c#') return scanCSharp(input);
|
|
833
|
+
if (language === 'php') return scanPhp(input);
|
|
834
|
+
if (language === 'ruby' || language === 'rb') return scanRuby(input);
|
|
835
|
+
if (language === 'kotlin' || language === 'kt') return scanKotlin(input);
|
|
836
|
+
if (language === 'scala' || language === 'sc') return scanScala(input);
|
|
837
|
+
if (language === 'dart') return scanDart(input);
|
|
838
|
+
if (language === 'lua') return scanLua(input);
|
|
839
|
+
if (language === 'shell' || language === 'sh' || language === 'bash' || language === 'zsh') return scanShell(input);
|
|
840
|
+
if (language === 'sql' || language === 'postgresql' || language === 'postgres' || language === 'mysql' || language === 'sqlite') return scanSql(input);
|
|
841
|
+
if (language === 'zig') return scanZig(input);
|
|
842
|
+
if (language === 'elixir' || language === 'ex' || language === 'exs') return scanElixir(input);
|
|
843
|
+
if (language === 'erlang' || language === 'erl' || language === 'hrl') return scanErlang(input);
|
|
844
|
+
if (language === 'haskell' || language === 'hs') return scanHaskell(input);
|
|
845
|
+
if (language === 'r') return scanR(input);
|
|
502
846
|
return scanGenericDeclarations(input);
|
|
503
847
|
}
|
|
504
848
|
|
|
@@ -588,116 +932,1057 @@ function scanCLike(input) {
|
|
|
588
932
|
return declarations;
|
|
589
933
|
}
|
|
590
934
|
|
|
591
|
-
function
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
935
|
+
function scanJava(input) {
|
|
936
|
+
const declarations = [];
|
|
937
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
938
|
+
const trimmed = line.trim();
|
|
939
|
+
let match;
|
|
940
|
+
if ((match = trimmed.match(/^package\s+([A-Za-z_][\w.]*);/))) {
|
|
941
|
+
declarations.push(nativeDeclaration(input, number, 'PackageDeclaration', 'package', match[1], {}, false));
|
|
942
|
+
} else if ((match = trimmed.match(/^import\s+(?:static\s+)?([A-Za-z_][\w.*]*);/))) {
|
|
943
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'package'));
|
|
944
|
+
} else if ((match = trimmed.match(/^(?:(?:public|protected|private|abstract|final|static|sealed|non-sealed)\s+)*(class|interface|enum|record|@interface)\s+([A-Za-z_$][\w$]*)/))) {
|
|
945
|
+
const kind = match[1] === '@interface' ? 'AnnotationDeclaration' : `${upperFirst(match[1])}Declaration`;
|
|
946
|
+
declarations.push(nativeDeclaration(input, number, kind, javaSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
|
|
947
|
+
} else if ((match = trimmed.match(/^(?:(?:public|protected|private|abstract|final|static|synchronized|native)\s+)*(?:<[^>]+>\s+)?[A-Za-z_$][\w$<>\[\].?,\s]*\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*(?:throws\s+[^{]+)?(?:\{|;)?$/))) {
|
|
948
|
+
declarations.push(nativeDeclaration(input, number, 'MethodDeclaration', 'method', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return declarations;
|
|
595
952
|
}
|
|
596
953
|
|
|
597
|
-
function
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
954
|
+
function scanGo(input) {
|
|
955
|
+
const declarations = [];
|
|
956
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
957
|
+
const trimmed = line.trim();
|
|
958
|
+
let match;
|
|
959
|
+
if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*)/))) {
|
|
960
|
+
declarations.push(nativeDeclaration(input, number, 'PackageClause', 'package', match[1], {}, false));
|
|
961
|
+
} else if ((match = trimmed.match(/^import\s+(?:[A-Za-z_]\w*\s+)?["']([^"']+)["']/))) {
|
|
962
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportSpec', 'package'));
|
|
963
|
+
} else if ((match = trimmed.match(/^type\s+([A-Za-z_]\w*)\s+(struct|interface)\b/))) {
|
|
964
|
+
declarations.push(nativeDeclaration(input, number, match[2] === 'struct' ? 'TypeSpecStruct' : 'TypeSpecInterface', 'type', match[1], {}, trimmed.includes('{')));
|
|
965
|
+
} else if ((match = trimmed.match(/^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
|
|
966
|
+
declarations.push(nativeDeclaration(input, number, 'FuncDecl', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
967
|
+
} else if ((match = trimmed.match(/^var\s+([A-Za-z_]\w*)\b/))) {
|
|
968
|
+
declarations.push(nativeDeclaration(input, number, 'VarDecl', 'variable', match[1], {}, false));
|
|
969
|
+
} else if ((match = trimmed.match(/^const\s+([A-Za-z_]\w*)\b/))) {
|
|
970
|
+
declarations.push(nativeDeclaration(input, number, 'ConstDecl', 'constant', match[1], {}, false));
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return declarations;
|
|
611
974
|
}
|
|
612
975
|
|
|
613
|
-
function
|
|
614
|
-
const
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
976
|
+
function scanSwift(input) {
|
|
977
|
+
const declarations = [];
|
|
978
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
979
|
+
const trimmed = line.trim();
|
|
980
|
+
let match;
|
|
981
|
+
if ((match = trimmed.match(/^import\s+([A-Za-z_]\w*)/))) {
|
|
982
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDecl', 'module'));
|
|
983
|
+
} else if ((match = trimmed.match(/^(?:(?:public|private|fileprivate|internal|open|final)\s+)*(struct|class|enum|protocol|actor|extension)\s+([A-Za-z_]\w*)/))) {
|
|
984
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Decl`, swiftSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
|
|
985
|
+
} else if ((match = trimmed.match(/^(?:(?:public|private|fileprivate|internal|open|static|class|mutating)\s+)*func\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
|
|
986
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDecl', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
987
|
+
} else if ((match = trimmed.match(/^(?:let|var)\s+([A-Za-z_]\w*)\b/))) {
|
|
988
|
+
declarations.push(nativeDeclaration(input, number, 'ValueBindingDecl', 'variable', match[1], {}, false));
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return declarations;
|
|
629
992
|
}
|
|
630
993
|
|
|
631
|
-
function
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
id: `loss_${idFragment(nodeId)}`,
|
|
645
|
-
severity: 'warning',
|
|
646
|
-
phase: 'read',
|
|
647
|
-
sourceFormat: input.language,
|
|
648
|
-
kind,
|
|
649
|
-
message: `${input.language} ${kind} retained as native source; expansion is not evaluated by the lightweight importer.`,
|
|
650
|
-
span: spanForLine(input, lineNumber),
|
|
651
|
-
nodeId
|
|
994
|
+
function scanCSharp(input) {
|
|
995
|
+
const declarations = [];
|
|
996
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
997
|
+
const trimmed = line.trim();
|
|
998
|
+
let match;
|
|
999
|
+
if ((match = trimmed.match(/^using\s+(?:static\s+)?([A-Za-z_][\w.]*)\s*;/))) {
|
|
1000
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'UsingDirective', 'namespace'));
|
|
1001
|
+
} else if ((match = trimmed.match(/^namespace\s+([A-Za-z_][\w.]*)/))) {
|
|
1002
|
+
declarations.push(nativeDeclaration(input, number, 'NamespaceDeclaration', 'namespace', match[1], {}, trimmed.includes('{')));
|
|
1003
|
+
} else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|abstract|sealed|static|partial|readonly)\s+)*(class|interface|struct|enum|record)\s+([A-Za-z_]\w*)/))) {
|
|
1004
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, csharpSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
|
|
1005
|
+
} else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|static|virtual|override|async|partial|sealed|abstract|extern)\s+)*[A-Za-z_][\w<>\[\].?,\s]*\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*(?:\{|;)?$/))) {
|
|
1006
|
+
declarations.push(nativeDeclaration(input, number, 'MethodDeclaration', 'method', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
652
1007
|
}
|
|
653
|
-
}
|
|
1008
|
+
}
|
|
1009
|
+
return declarations;
|
|
654
1010
|
}
|
|
655
1011
|
|
|
656
|
-
function
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1012
|
+
function scanPhp(input) {
|
|
1013
|
+
const declarations = [];
|
|
1014
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1015
|
+
const trimmed = line.trim().replace(/^<\?php\s*/, '');
|
|
1016
|
+
let match;
|
|
1017
|
+
if ((match = trimmed.match(/^namespace\s+([A-Za-z_][\w\\]*)\s*;/))) {
|
|
1018
|
+
declarations.push(nativeDeclaration(input, number, 'NamespaceDefinition', 'namespace', match[1], {}, false));
|
|
1019
|
+
} else if ((match = trimmed.match(/^use\s+([A-Za-z_][\w\\]*)(?:\s+as\s+([A-Za-z_]\w*))?\s*;/))) {
|
|
1020
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'UseDeclaration', 'namespace'));
|
|
1021
|
+
} else if ((match = trimmed.match(/^(?:(?:abstract|final|readonly)\s+)*(class|interface|trait|enum)\s+([A-Za-z_]\w*)/))) {
|
|
1022
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, phpSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
|
|
1023
|
+
} else if ((match = trimmed.match(/^(?:(?:public|protected|private|static|final|abstract)\s+)*function\s+&?\s*([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
|
|
1024
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return declarations;
|
|
667
1028
|
}
|
|
668
1029
|
|
|
669
|
-
function
|
|
670
|
-
|
|
1030
|
+
function scanRuby(input) {
|
|
1031
|
+
const declarations = [];
|
|
1032
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1033
|
+
const trimmed = line.trim();
|
|
1034
|
+
let match;
|
|
1035
|
+
if ((match = trimmed.match(/^(?:require|load)\s+['"]([^'"]+)['"]/))) {
|
|
1036
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'Require', 'module'));
|
|
1037
|
+
} else if ((match = trimmed.match(/^module\s+([A-Za-z_]\w*(?:::[A-Za-z_]\w*)*)/))) {
|
|
1038
|
+
declarations.push(nativeDeclaration(input, number, 'Module', 'module', match[1], {}, true));
|
|
1039
|
+
} else if ((match = trimmed.match(/^class\s+([A-Za-z_]\w*(?:::[A-Za-z_]\w*)*)/))) {
|
|
1040
|
+
declarations.push(nativeDeclaration(input, number, 'Class', 'class', match[1], {}, true));
|
|
1041
|
+
} else if ((match = trimmed.match(/^def\s+(?:self\.)?([A-Za-z_]\w*[!?=]?)\s*(?:\(([^)]*)\)|([^#=]*))?/))) {
|
|
1042
|
+
declarations.push(nativeDeclaration(input, number, 'Def', 'method', match[1], { parameters: splitParameters(match[2] ?? match[3]) }, true));
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return declarations;
|
|
671
1046
|
}
|
|
672
1047
|
|
|
673
|
-
function
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
1048
|
+
function scanKotlin(input) {
|
|
1049
|
+
const declarations = [];
|
|
1050
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1051
|
+
const trimmed = line.trim();
|
|
1052
|
+
let match;
|
|
1053
|
+
if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/))) {
|
|
1054
|
+
declarations.push(nativeDeclaration(input, number, 'PackageHeader', 'package', match[1], {}, false));
|
|
1055
|
+
} else if ((match = trimmed.match(/^import\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*(?:\.\*)?)(?:\s+as\s+[A-Za-z_]\w*)?$/))) {
|
|
1056
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDirective', 'package'));
|
|
1057
|
+
} else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual|open|final|abstract|sealed|data|value)\s+)*(?:(enum|annotation)\s+)?(class|interface|object)\s+([A-Za-z_]\w*)/))) {
|
|
1058
|
+
declarations.push(nativeDeclaration(input, number, kotlinDeclarationKind(match[2], match[1]), kotlinSymbolKind(match[2], match[1]), match[3], {}, trimmed.includes('{')));
|
|
1059
|
+
} else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual|open|final|abstract|inline|tailrec|operator|infix|external|suspend|override)\s+)*fun\s+(?:<[^>]+>\s*)?(?:[A-Za-z_][\w.<>?]*\.)?([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
|
|
1060
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=')));
|
|
1061
|
+
} else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual)\s+)*typealias\s+([A-Za-z_]\w*)\s*=/))) {
|
|
1062
|
+
declarations.push(nativeDeclaration(input, number, 'TypeAliasDeclaration', 'type', match[1], {}, false));
|
|
1063
|
+
} else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual|open|final|abstract|override|const|lateinit)\s+)*(?:val|var)\s+([A-Za-z_]\w*)\b/))) {
|
|
1064
|
+
declarations.push(nativeDeclaration(input, number, 'PropertyDeclaration', 'variable', match[1], {}, false));
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return declarations;
|
|
681
1068
|
}
|
|
682
1069
|
|
|
683
|
-
function
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1070
|
+
function scanScala(input) {
|
|
1071
|
+
const declarations = [];
|
|
1072
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1073
|
+
const trimmed = line.trim();
|
|
1074
|
+
let match;
|
|
1075
|
+
if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/))) {
|
|
1076
|
+
declarations.push(nativeDeclaration(input, number, 'PackageClause', 'package', match[1], {}, false));
|
|
1077
|
+
} else if ((match = trimmed.match(/^import\s+(.+?);?$/))) {
|
|
1078
|
+
declarations.push(nativeImportDeclaration(input, number, match[1].trim(), 'Import', 'package'));
|
|
1079
|
+
} else if ((match = trimmed.match(/^(?:(?:private|protected|final|sealed|abstract|case|implicit|lazy|override|inline|transparent|open)\s+)*(class|trait|object|enum)\s+([A-Za-z_]\w*)/))) {
|
|
1080
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Def`, scalaSymbolKind(match[1]), match[2], {}, trimmed.includes('{') || trimmed.includes(':')));
|
|
1081
|
+
} else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|override|inline)\s+)*def\s+([A-Za-z_]\w*)\s*(?:\[[^\]]+\])?\s*\(([^)]*)\)/))) {
|
|
1082
|
+
declarations.push(nativeDeclaration(input, number, 'DefDef', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=')));
|
|
1083
|
+
} else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|opaque)\s+)*type\s+([A-Za-z_]\w*)\b/))) {
|
|
1084
|
+
declarations.push(nativeDeclaration(input, number, 'TypeDef', 'type', match[1], {}, false));
|
|
1085
|
+
} else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|lazy|override|inline)\s+)*(?:val|var)\s+([A-Za-z_]\w*)\b/))) {
|
|
1086
|
+
declarations.push(nativeDeclaration(input, number, 'ValDef', 'variable', match[1], {}, false));
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return declarations;
|
|
688
1090
|
}
|
|
689
1091
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1092
|
+
function scanDart(input) {
|
|
1093
|
+
const declarations = [];
|
|
1094
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1095
|
+
const trimmed = line.trim();
|
|
1096
|
+
let match;
|
|
1097
|
+
if ((match = trimmed.match(/^(?:import|export)\s+['"]([^'"]+)['"]/))) {
|
|
1098
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'UriBasedDirective', 'library'));
|
|
1099
|
+
} else if ((match = trimmed.match(/^part\s+['"]([^'"]+)['"]/))) {
|
|
1100
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'PartDirective', 'library'));
|
|
1101
|
+
} else if ((match = trimmed.match(/^(?:(?:abstract|base|final|interface|sealed)\s+)*(class|mixin|enum)\s+([A-Za-z_]\w*)/))) {
|
|
1102
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, dartSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
|
|
1103
|
+
} else if ((match = trimmed.match(/^extension\s+([A-Za-z_]\w*)\s+on\s+.+\{/))) {
|
|
1104
|
+
declarations.push(nativeDeclaration(input, number, 'ExtensionDeclaration', 'implementation', match[1], {}, true));
|
|
1105
|
+
} else if ((match = trimmed.match(/^typedef\s+([A-Za-z_]\w*)\b/))) {
|
|
1106
|
+
declarations.push(nativeDeclaration(input, number, 'TypeAlias', 'type', match[1], {}, false));
|
|
1107
|
+
} else if ((match = trimmed.match(/^(?:(?:external|static)\s+)*(?:[A-Za-z_]\w*(?:<[^>]+>)?\??|void)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*(?:async\s*)?(?:\{|=>|;)/))) {
|
|
1108
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=>')));
|
|
1109
|
+
} else if ((match = trimmed.match(/^(?:(?:static|external|late)\s+)*(?:const|final|var)\s+(?:[A-Za-z_]\w*(?:<[^>]+>)?\??\s+)?([A-Za-z_]\w*)\b/))) {
|
|
1110
|
+
declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', 'variable', match[1], {}, false));
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return declarations;
|
|
698
1114
|
}
|
|
699
1115
|
|
|
700
|
-
|
|
1116
|
+
function scanLua(input) {
|
|
1117
|
+
const declarations = [];
|
|
1118
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1119
|
+
const trimmed = line.trim();
|
|
1120
|
+
let match;
|
|
1121
|
+
if ((match = trimmed.match(/^(?:local\s+[A-Za-z_]\w*\s*=\s*)?require\s*\(?\s*['"]([^'"]+)['"]\s*\)?/))) {
|
|
1122
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'RequireCall', 'module'));
|
|
1123
|
+
} else if ((match = trimmed.match(/^(?:local\s+)?function\s+([A-Za-z_]\w*(?:[.:][A-Za-z_]\w*)*)\s*\(([^)]*)\)/))) {
|
|
1124
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
|
|
1125
|
+
} else if ((match = trimmed.match(/^(?:local\s+)?([A-Za-z_]\w*(?:[.:][A-Za-z_]\w*)*)\s*=\s*function\s*\(([^)]*)\)/))) {
|
|
1126
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionAssignment', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
|
|
1127
|
+
} else if ((match = trimmed.match(/^local\s+([A-Za-z_]\w*)\s*=\s*(?:\{|\w+)/))) {
|
|
1128
|
+
declarations.push(nativeDeclaration(input, number, 'LocalDeclaration', 'variable', match[1], {}, false));
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return declarations;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function scanShell(input) {
|
|
1135
|
+
const declarations = [];
|
|
1136
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1137
|
+
const trimmed = line.trim();
|
|
1138
|
+
let match;
|
|
1139
|
+
if ((match = trimmed.match(/^(?:source|\.)\s+(?:"([^"]+)"|'([^']+)'|([./A-Za-z0-9_-][\w./-]*))(?:\s|$)/))) {
|
|
1140
|
+
declarations.push(nativeImportDeclaration(input, number, match[1] ?? match[2] ?? match[3], 'SourceCommand', 'file'));
|
|
1141
|
+
} else if ((match = trimmed.match(/^function\s+([A-Za-z_][\w-]*)\s*(?:\(\s*\))?\s*(?:\{|$)/))) {
|
|
1142
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], {}, true));
|
|
1143
|
+
} else if ((match = trimmed.match(/^([A-Za-z_][\w-]*)\s*\(\s*\)\s*(?:\{|$)/))) {
|
|
1144
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], {}, true));
|
|
1145
|
+
} else if ((match = trimmed.match(/^(?:export\s+)?(?:readonly\s+)?([A-Za-z_]\w*)=/))) {
|
|
1146
|
+
declarations.push(nativeDeclaration(input, number, 'VariableAssignment', 'variable', match[1], {}, false));
|
|
1147
|
+
} else if ((match = trimmed.match(/^alias\s+([A-Za-z_][\w-]*)=/))) {
|
|
1148
|
+
declarations.push(nativeDeclaration(input, number, 'AliasDeclaration', 'function', match[1], {}, false));
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return declarations;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function scanSql(input) {
|
|
1155
|
+
const declarations = [];
|
|
1156
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1157
|
+
const trimmed = line.trim();
|
|
1158
|
+
let match;
|
|
1159
|
+
if ((match = trimmed.match(/^CREATE\s+EXTENSION\s+(?:IF\s+NOT\s+EXISTS\s+)?((?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$-]*))/i))) {
|
|
1160
|
+
declarations.push(nativeImportDeclaration(input, number, normalizeSqlIdentifier(match[1]), 'CreateExtensionStatement', 'extension'));
|
|
1161
|
+
} else if ((match = trimmed.match(/^CREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP(?:ORARY)?\s+)?((?:UNIQUE\s+)?INDEX|MATERIALIZED\s+VIEW|TABLE|VIEW|FUNCTION|PROCEDURE|TRIGGER|SCHEMA|TYPE)\s+(?:IF\s+NOT\s+EXISTS\s+)?((?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$]*)(?:\s*\.\s*(?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$]*))?)/i))) {
|
|
1162
|
+
const objectKind = match[1].toUpperCase().replace(/\s+/g, ' ');
|
|
1163
|
+
declarations.push(nativeDeclaration(input, number, sqlLanguageKind(objectKind), sqlSymbolKind(objectKind), normalizeSqlIdentifier(match[2]), { objectKind }, trimmed.includes('(')));
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return declarations;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function scanZig(input) {
|
|
1170
|
+
const declarations = [];
|
|
1171
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1172
|
+
const trimmed = line.trim();
|
|
1173
|
+
let match;
|
|
1174
|
+
if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?(?:const|var)\s+([A-Za-z_]\w*)\s*=\s*@import\(\s*["']([^"']+)["']\s*\)\s*;?/))) {
|
|
1175
|
+
declarations.push(nativeImportDeclaration(input, number, match[2], 'ImportDeclaration', 'module'));
|
|
1176
|
+
} else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?usingnamespace\s+@import\(\s*["']([^"']+)["']\s*\)\s*;?/))) {
|
|
1177
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'UsingNamespaceImport', 'module'));
|
|
1178
|
+
} else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?const\s+([A-Za-z_]\w*)\s*=\s*(?:extern\s+)?(struct|enum|union|opaque|error)\b/))) {
|
|
1179
|
+
declarations.push(nativeDeclaration(input, number, `Const${upperFirst(match[2])}Declaration`, 'type', match[1], { zigKind: match[2] }, trimmed.includes('{')));
|
|
1180
|
+
} else if ((match = trimmed.match(/^(?:(?:pub|export|extern|inline)\s+)*(?:fn)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
|
|
1181
|
+
declarations.push(nativeDeclaration(input, number, 'FnDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
1182
|
+
} else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?const\s+([A-Za-z_]\w*)\b/))) {
|
|
1183
|
+
declarations.push(nativeDeclaration(input, number, 'ConstDeclaration', 'constant', match[1], {}, false));
|
|
1184
|
+
} else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?var\s+([A-Za-z_]\w*)\b/))) {
|
|
1185
|
+
declarations.push(nativeDeclaration(input, number, 'VarDeclaration', 'variable', match[1], {}, false));
|
|
1186
|
+
}
|
|
1187
|
+
if (/^\s*comptime\b|@(?:cImport|compileError|field|hasDecl|hasField|setEvalBranchQuota|This|Type|typeInfo)\b/.test(trimmed)) {
|
|
1188
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'generatedCode', zigMetaName(trimmed)));
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return declarations;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function scanElixir(input) {
|
|
1195
|
+
const declarations = [];
|
|
1196
|
+
let currentModule;
|
|
1197
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1198
|
+
const trimmed = line.trim();
|
|
1199
|
+
let match;
|
|
1200
|
+
let recordedMeta = false;
|
|
1201
|
+
if ((match = trimmed.match(/^defmodule\s+([A-Z]\w*(?:\.[A-Z]\w*)*)\s+do\b/))) {
|
|
1202
|
+
currentModule = match[1];
|
|
1203
|
+
declarations.push(nativeDeclaration(input, number, 'ModuleDefinition', 'module', match[1], {}, true));
|
|
1204
|
+
} else if ((match = trimmed.match(/^(?:alias|import|require)\s+([A-Z]\w*(?:\.[A-Z]\w*)*)/))) {
|
|
1205
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDirective', 'module'));
|
|
1206
|
+
} else if ((match = trimmed.match(/^use\s+([A-Z]\w*(?:\.[A-Z]\w*)*)/))) {
|
|
1207
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', match[1]));
|
|
1208
|
+
recordedMeta = true;
|
|
1209
|
+
} else if ((match = trimmed.match(/^(defmacro|defmacrop|defguard|defguardp|defdelegate)\s+([A-Za-z_]\w*[!?]?)/))) {
|
|
1210
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', match[2]));
|
|
1211
|
+
recordedMeta = true;
|
|
1212
|
+
} else if ((match = trimmed.match(/^defp?\s+([A-Za-z_]\w*[!?]?)\s*(?:\(([^)]*)\)|([^,]*))?/))) {
|
|
1213
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], { parameters: splitParameters(match[2] ?? match[3]) }, /\bdo\b/.test(trimmed)));
|
|
1214
|
+
} else if (trimmed.startsWith('defstruct')) {
|
|
1215
|
+
declarations.push(nativeDeclaration(input, number, 'StructDefinition', 'type', currentModule ?? `struct_${number}`, {}, true));
|
|
1216
|
+
} else if ((match = trimmed.match(/^@(type|typep|opaque|callback)\s+([A-Za-z_]\w*[!?]?)/))) {
|
|
1217
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Attribute`, match[1] === 'callback' ? 'function' : 'type', match[2], {}, false));
|
|
1218
|
+
}
|
|
1219
|
+
if (!recordedMeta && /(?:\bquote\s+do\b|\bunquote(?:_splicing)?\b|@(?:before_compile|after_compile|on_definition|derive)\b)/.test(trimmed)) {
|
|
1220
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', elixirMetaName(trimmed)));
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return declarations;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function scanErlang(input) {
|
|
1227
|
+
const declarations = [];
|
|
1228
|
+
const seenFunctions = new Set();
|
|
1229
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1230
|
+
const trimmed = line.trim();
|
|
1231
|
+
let match;
|
|
1232
|
+
let recordedMacro = false;
|
|
1233
|
+
if ((match = trimmed.match(/^-module\(([a-z][A-Za-z0-9_@]*)\)\./))) {
|
|
1234
|
+
declarations.push(nativeDeclaration(input, number, 'ModuleAttribute', 'module', match[1], {}, false));
|
|
1235
|
+
} else if ((match = trimmed.match(/^-include(?:_lib)?\(["']([^"']+)["']\)\./))) {
|
|
1236
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'IncludeAttribute', 'module'));
|
|
1237
|
+
} else if ((match = trimmed.match(/^-import\(([a-z][A-Za-z0-9_@]*)\s*,/))) {
|
|
1238
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportAttribute', 'module'));
|
|
1239
|
+
} else if ((match = trimmed.match(/^-behaviou?r\(([a-z][A-Za-z0-9_@]*)\)\./))) {
|
|
1240
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'BehaviourAttribute', 'module'));
|
|
1241
|
+
} else if ((match = trimmed.match(/^-record\(([a-z][A-Za-z0-9_@]*)\s*,/))) {
|
|
1242
|
+
declarations.push(nativeDeclaration(input, number, 'RecordAttribute', 'type', match[1], {}, false));
|
|
1243
|
+
} else if ((match = trimmed.match(/^-(type|opaque)\s+([a-z][A-Za-z0-9_@]*)\s*\(/))) {
|
|
1244
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Attribute`, 'type', match[2], {}, false));
|
|
1245
|
+
} else if ((match = trimmed.match(/^-callback\s+([a-z][A-Za-z0-9_@]*)\s*\(/))) {
|
|
1246
|
+
declarations.push(nativeDeclaration(input, number, 'CallbackAttribute', 'function', match[1], {}, false));
|
|
1247
|
+
} else if ((match = trimmed.match(/^-define\(([^,\s)]+)/))) {
|
|
1248
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'preprocessor', match[1]));
|
|
1249
|
+
recordedMacro = true;
|
|
1250
|
+
} else if (/^-compile\([^)]*parse_transform/.test(trimmed)) {
|
|
1251
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'generatedCode', 'parse_transform'));
|
|
1252
|
+
recordedMacro = true;
|
|
1253
|
+
} else if ((match = trimmed.match(/^([a-z][A-Za-z0-9_@]*|'[^']+')\s*\(([^)]*)\)\s*(?:when\s+.*?)?->/))) {
|
|
1254
|
+
const name = erlangAtomName(match[1]);
|
|
1255
|
+
if (!seenFunctions.has(name)) {
|
|
1256
|
+
seenFunctions.add(name);
|
|
1257
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionClause', 'function', name, { parameters: splitParameters(match[2]) }, true));
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (!recordedMacro && /(^|[^A-Za-z0-9_])\?[A-Za-z_]\w*/.test(trimmed)) {
|
|
1261
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', erlangMacroName(trimmed)));
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
return declarations;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
function scanHaskell(input) {
|
|
1268
|
+
const declarations = [];
|
|
1269
|
+
const seenFunctions = new Set();
|
|
1270
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1271
|
+
const trimmed = line.trim();
|
|
1272
|
+
let match;
|
|
1273
|
+
if (/^#\s*(?:if|ifdef|ifndef|else|elif|endif|define|include)\b/.test(trimmed)) {
|
|
1274
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'preprocessor', haskellMetaName(trimmed)));
|
|
1275
|
+
} else if ((match = trimmed.match(/^\{-#\s+LANGUAGE\s+(.+?)#-\}/))) {
|
|
1276
|
+
const extensions = match[1].split(',').map((part) => part.trim());
|
|
1277
|
+
if (extensions.some((extension) => /^(TemplateHaskell|QuasiQuotes|CPP)$/.test(extension))) {
|
|
1278
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, extensions.includes('CPP') ? 'preprocessor' : 'macroExpansion', extensions.join('_')));
|
|
1279
|
+
}
|
|
1280
|
+
} else if (/^\$\(|\[[a-zA-Z]*\||\b\$\([^)]+\)/.test(trimmed)) {
|
|
1281
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', haskellMetaName(trimmed)));
|
|
1282
|
+
} else if ((match = trimmed.match(/^module\s+([A-Z][A-Za-z0-9_.']*)\b/))) {
|
|
1283
|
+
declarations.push(nativeDeclaration(input, number, 'ModuleDeclaration', 'module', match[1], {}, false));
|
|
1284
|
+
} else if ((match = trimmed.match(/^import\s+(?:safe\s+)?(?:qualified\s+)?([A-Z][A-Za-z0-9_.']*)/))) {
|
|
1285
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'module'));
|
|
1286
|
+
} else if ((match = trimmed.match(/^foreign\s+import\s+([A-Za-z_]\w*)/))) {
|
|
1287
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ForeignImportDeclaration', 'foreign'));
|
|
1288
|
+
} else if ((match = trimmed.match(/^(data|newtype|type)\s+([A-Z][A-Za-z0-9_']*)\b/))) {
|
|
1289
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, 'type', match[2], {}, /(?:where|=)/.test(trimmed)));
|
|
1290
|
+
} else if ((match = trimmed.match(/^class\s+(?:\([^)]*\)\s*=>\s*)?([A-Z][A-Za-z0-9_']*)\b/))) {
|
|
1291
|
+
declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'type', match[1], {}, /\bwhere\b/.test(trimmed)));
|
|
1292
|
+
} else if ((match = trimmed.match(/^([a-z_][A-Za-z0-9_']*)\s*::\s*(.+)$/))) {
|
|
1293
|
+
seenFunctions.add(match[1]);
|
|
1294
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionSignature', 'function', match[1], { signature: match[2].trim() }, false));
|
|
1295
|
+
} else if ((match = trimmed.match(/^([a-z_][A-Za-z0-9_']*)\b[^=]*=/))) {
|
|
1296
|
+
if (!seenFunctions.has(match[1])) {
|
|
1297
|
+
seenFunctions.add(match[1]);
|
|
1298
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionBinding', 'function', match[1], {}, true));
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
return declarations;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
function scanR(input) {
|
|
1306
|
+
const declarations = [];
|
|
1307
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1308
|
+
const trimmed = line.trim();
|
|
1309
|
+
let match;
|
|
1310
|
+
if ((match = trimmed.match(/^(?:library|require)\s*\(\s*["']?([A-Za-z_][\w.-]*)["']?/))) {
|
|
1311
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'LibraryCall', 'package'));
|
|
1312
|
+
} else if ((match = trimmed.match(/^importFrom\s*\(\s*["']?([A-Za-z_][\w.-]*)["']?/))) {
|
|
1313
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportFromCall', 'package'));
|
|
1314
|
+
} else if ((match = trimmed.match(/^source\s*\(\s*["']([^"']+)["']/))) {
|
|
1315
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'SourceCall', 'module'));
|
|
1316
|
+
} else if ((match = trimmed.match(/^([A-Za-z_][\w.]*)\s*(?:<-|=)\s*function\s*\(([^)]*)\)/))) {
|
|
1317
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionAssignment', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
1318
|
+
} else if ((match = trimmed.match(/^([A-Za-z_][\w.]*)\s*<-\s*R6Class\s*\(\s*["']([^"']+)["']/))) {
|
|
1319
|
+
declarations.push(nativeDeclaration(input, number, 'R6ClassDeclaration', 'class', match[2] || match[1], { binding: match[1] }, true));
|
|
1320
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[2] || match[1]));
|
|
1321
|
+
} else if ((match = trimmed.match(/^setClass\s*\(\s*["']([^"']+)["']/))) {
|
|
1322
|
+
declarations.push(nativeDeclaration(input, number, 'S4ClassDeclaration', 'class', match[1], {}, true));
|
|
1323
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[1]));
|
|
1324
|
+
} else if ((match = trimmed.match(/^setGeneric\s*\(\s*["']([^"']+)["']/))) {
|
|
1325
|
+
declarations.push(nativeDeclaration(input, number, 'S4GenericDeclaration', 'function', match[1], {}, true));
|
|
1326
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[1]));
|
|
1327
|
+
} else if ((match = trimmed.match(/^setMethod\s*\(\s*["']([^"']+)["']/))) {
|
|
1328
|
+
declarations.push(nativeDeclaration(input, number, 'S4MethodDeclaration', 'method', match[1], {}, true));
|
|
1329
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicDispatch', match[1]));
|
|
1330
|
+
} else if ((match = trimmed.match(/^([A-Z][A-Za-z0-9_.]*)\s*(?:<-|=)\s*/))) {
|
|
1331
|
+
declarations.push(nativeDeclaration(input, number, 'ConstantAssignment', 'constant', match[1], {}, false));
|
|
1332
|
+
}
|
|
1333
|
+
if (/(?:eval|parse|substitute|quote|bquote|assign)\s*\(|<<-|\{\{|!!!|!!/.test(trimmed)) {
|
|
1334
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', rMetaName(trimmed)));
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return declarations;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function scanGenericDeclarations(input) {
|
|
1341
|
+
return sourceLines(input.sourceText)
|
|
1342
|
+
.filter(({ line }) => /\b(function|class|struct|enum|trait|interface|def)\b/.test(line))
|
|
1343
|
+
.map(({ line, number }) => nativeDeclaration(input, number, 'NativeDeclaration', 'variable', idFragment(line.trim()).slice(0, 40), { source: line.trim() }, true));
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function upperFirst(value) {
|
|
1347
|
+
return String(value).charAt(0).toUpperCase() + String(value).slice(1);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
function javaSymbolKind(kind) {
|
|
1351
|
+
if (kind === 'interface' || kind === '@interface') return 'interface';
|
|
1352
|
+
if (kind === 'enum' || kind === 'record') return 'type';
|
|
1353
|
+
return 'class';
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function swiftSymbolKind(kind) {
|
|
1357
|
+
if (kind === 'protocol') return 'protocol';
|
|
1358
|
+
if (kind === 'extension') return 'implementation';
|
|
1359
|
+
if (kind === 'struct' || kind === 'enum' || kind === 'actor') return 'type';
|
|
1360
|
+
return 'class';
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function csharpSymbolKind(kind) {
|
|
1364
|
+
if (kind === 'interface') return 'interface';
|
|
1365
|
+
if (kind === 'struct' || kind === 'enum' || kind === 'record') return 'type';
|
|
1366
|
+
return 'class';
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function phpSymbolKind(kind) {
|
|
1370
|
+
if (kind === 'interface') return 'interface';
|
|
1371
|
+
if (kind === 'trait') return 'trait';
|
|
1372
|
+
if (kind === 'enum') return 'type';
|
|
1373
|
+
return 'class';
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function kotlinDeclarationKind(kind, prefix) {
|
|
1377
|
+
if (prefix === 'enum') return 'EnumClassDeclaration';
|
|
1378
|
+
if (prefix === 'annotation') return 'AnnotationClassDeclaration';
|
|
1379
|
+
return `${upperFirst(kind)}Declaration`;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function kotlinSymbolKind(kind, prefix) {
|
|
1383
|
+
if (kind === 'interface') return 'interface';
|
|
1384
|
+
if (kind === 'object') return 'module';
|
|
1385
|
+
if (prefix === 'enum' || prefix === 'annotation') return 'type';
|
|
1386
|
+
return 'class';
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
function scalaSymbolKind(kind) {
|
|
1390
|
+
if (kind === 'trait') return 'trait';
|
|
1391
|
+
if (kind === 'object') return 'module';
|
|
1392
|
+
if (kind === 'enum') return 'type';
|
|
1393
|
+
return 'class';
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function dartSymbolKind(kind) {
|
|
1397
|
+
if (kind === 'mixin') return 'trait';
|
|
1398
|
+
if (kind === 'enum') return 'type';
|
|
1399
|
+
return 'class';
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
function sqlSymbolKind(kind) {
|
|
1403
|
+
if (kind === 'FUNCTION' || kind === 'PROCEDURE' || kind === 'TRIGGER') return 'function';
|
|
1404
|
+
if (kind.includes('INDEX')) return 'index';
|
|
1405
|
+
if (kind === 'SCHEMA') return 'namespace';
|
|
1406
|
+
return 'type';
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
function sqlLanguageKind(kind) {
|
|
1410
|
+
return `Create${kind.toLowerCase().split(/\s+/).map(upperFirst).join('')}Statement`;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
function normalizeSqlIdentifier(value) {
|
|
1414
|
+
return String(value)
|
|
1415
|
+
.split(/\s*\.\s*/)
|
|
1416
|
+
.map((part) => part.replace(/^"|"$/g, '').replace(/^`|`$/g, '').replace(/^\[|\]$/g, ''))
|
|
1417
|
+
.join('.');
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
function zigMetaName(source) {
|
|
1421
|
+
const match = source.match(/@([A-Za-z_]\w*)|^\s*(comptime)\b/);
|
|
1422
|
+
return match?.[1] ?? match?.[2] ?? 'comptime';
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
function elixirMetaName(source) {
|
|
1426
|
+
const match = source.match(/@([A-Za-z_]\w*)|\b(unquote(?:_splicing)?|quote)\b/);
|
|
1427
|
+
return match?.[1] ?? match?.[2] ?? 'macro';
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
function erlangAtomName(value) {
|
|
1431
|
+
return String(value).startsWith("'") ? String(value).slice(1, -1) : String(value);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function erlangMacroName(source) {
|
|
1435
|
+
const match = source.match(/\?([A-Za-z_]\w*)/);
|
|
1436
|
+
return match?.[1] ?? 'macro';
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
function haskellMetaName(source) {
|
|
1440
|
+
return idFragment(source).slice(0, 40);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
function rMetaName(source) {
|
|
1444
|
+
const match = source.match(/([A-Za-z_][\w.]*)\s*\(/);
|
|
1445
|
+
return match?.[1] ?? 'dynamic';
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fields = {}, hasBody = false) {
|
|
1449
|
+
const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
|
|
1450
|
+
return {
|
|
1451
|
+
nodeId,
|
|
1452
|
+
kind: languageKind,
|
|
1453
|
+
languageKind: `${input.language}.${languageKind}`,
|
|
1454
|
+
name,
|
|
1455
|
+
symbolKind,
|
|
1456
|
+
symbolId: `symbol:${input.language}:${idFragment(name)}`,
|
|
1457
|
+
span: spanForLine(input, lineNumber),
|
|
1458
|
+
fields,
|
|
1459
|
+
metadata: { scan: 'lightweight-declaration', hasBody },
|
|
1460
|
+
...(hasBody ? { loss: opaqueBodyLoss(input, lineNumber, nodeId, name) } : {})
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
function nativeImportDeclaration(input, lineNumber, importPath, languageKind, symbolKind) {
|
|
1465
|
+
const name = String(importPath);
|
|
1466
|
+
const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
|
|
1467
|
+
return {
|
|
1468
|
+
nodeId,
|
|
1469
|
+
kind: languageKind,
|
|
1470
|
+
languageKind: `${input.language}.${languageKind}`,
|
|
1471
|
+
name,
|
|
1472
|
+
symbolKind,
|
|
1473
|
+
symbolId: `symbol:${input.language}:import:${idFragment(name)}`,
|
|
1474
|
+
role: 'import',
|
|
1475
|
+
importPath: name,
|
|
1476
|
+
span: spanForLine(input, lineNumber),
|
|
1477
|
+
fields: { importPath: name },
|
|
1478
|
+
metadata: { scan: 'lightweight-import' }
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
function nativeMacroLoss(input, lineNumber, source, kind, name = idFragment(source).slice(0, 40)) {
|
|
1483
|
+
const nodeId = `native_${kind}_${lineNumber}_${idFragment(name)}`;
|
|
1484
|
+
return {
|
|
1485
|
+
nodeId,
|
|
1486
|
+
kind: kind === 'preprocessor' ? 'PreprocessorDirective' : 'MacroInvocation',
|
|
1487
|
+
languageKind: `${input.language}.${kind}`,
|
|
1488
|
+
name,
|
|
1489
|
+
symbolKind: 'constant',
|
|
1490
|
+
symbolId: `symbol:${input.language}:${kind}:${idFragment(name)}`,
|
|
1491
|
+
span: spanForLine(input, lineNumber),
|
|
1492
|
+
fields: { source },
|
|
1493
|
+
metadata: { scan: 'lightweight-macro' },
|
|
1494
|
+
loss: {
|
|
1495
|
+
id: `loss_${idFragment(nodeId)}`,
|
|
1496
|
+
severity: 'warning',
|
|
1497
|
+
phase: 'read',
|
|
1498
|
+
sourceFormat: input.language,
|
|
1499
|
+
kind,
|
|
1500
|
+
message: `${input.language} ${kind} retained as native source; expansion is not evaluated by the lightweight importer.`,
|
|
1501
|
+
span: spanForLine(input, lineNumber),
|
|
1502
|
+
nodeId
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
function opaqueBodyLoss(input, lineNumber, nodeId, name) {
|
|
1508
|
+
return {
|
|
1509
|
+
id: `loss_${idFragment(nodeId)}_body`,
|
|
1510
|
+
severity: 'info',
|
|
1511
|
+
phase: 'read',
|
|
1512
|
+
sourceFormat: input.language,
|
|
1513
|
+
kind: 'opaqueNative',
|
|
1514
|
+
message: `Body for ${name} is retained as native source by the lightweight declaration importer.`,
|
|
1515
|
+
span: spanForLine(input, lineNumber),
|
|
1516
|
+
nodeId
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
function lightweightCoverageLosses(input, declarations) {
|
|
1521
|
+
const id = idFragment(input.sourcePath ?? input.language);
|
|
1522
|
+
const span = declarations[0]?.span ?? {
|
|
1523
|
+
sourceId: input.sourceHash,
|
|
1524
|
+
path: input.sourcePath,
|
|
1525
|
+
startLine: 1,
|
|
1526
|
+
startColumn: 1
|
|
1527
|
+
};
|
|
1528
|
+
return [
|
|
1529
|
+
{
|
|
1530
|
+
id: `loss_${id}_declaration_only_coverage`,
|
|
1531
|
+
severity: 'info',
|
|
1532
|
+
phase: 'read',
|
|
1533
|
+
sourceFormat: input.language,
|
|
1534
|
+
kind: 'declarationOnlyCoverage',
|
|
1535
|
+
message: 'Lightweight importer scanned declarations and imports only; expressions, control flow, and full type checking were not evaluated.',
|
|
1536
|
+
span
|
|
1537
|
+
},
|
|
1538
|
+
{
|
|
1539
|
+
id: `loss_${id}_partial_semantic_index`,
|
|
1540
|
+
severity: 'info',
|
|
1541
|
+
phase: 'index',
|
|
1542
|
+
sourceFormat: input.language,
|
|
1543
|
+
kind: 'partialSemanticIndex',
|
|
1544
|
+
message: 'Semantic index contains lightweight declaration/import facts only; references, calls, resolved types, and cross-file links may be missing.',
|
|
1545
|
+
span
|
|
1546
|
+
},
|
|
1547
|
+
{
|
|
1548
|
+
id: `loss_${id}_source_map_approximation`,
|
|
1549
|
+
severity: 'info',
|
|
1550
|
+
phase: 'map',
|
|
1551
|
+
sourceFormat: input.language,
|
|
1552
|
+
kind: 'sourceMapApproximation',
|
|
1553
|
+
message: 'Source-map spans are declaration or line estimates; exact token ranges require a parser adapter.',
|
|
1554
|
+
span
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
id: `loss_${id}_source_preservation`,
|
|
1558
|
+
severity: 'warning',
|
|
1559
|
+
phase: 'read',
|
|
1560
|
+
sourceFormat: input.language,
|
|
1561
|
+
kind: 'sourcePreservation',
|
|
1562
|
+
message: 'Comments, whitespace, token order, directives, and formatting are not preserved by the lightweight importer.',
|
|
1563
|
+
span
|
|
1564
|
+
}
|
|
1565
|
+
];
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
function sourceLines(sourceText) {
|
|
1569
|
+
return String(sourceText ?? '').split(/\r?\n/).map((line, index) => ({ line, number: index + 1 }));
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
function spanForLine(input, lineNumber) {
|
|
1573
|
+
return {
|
|
1574
|
+
sourceId: input.sourceHash,
|
|
1575
|
+
path: input.sourcePath,
|
|
1576
|
+
startLine: lineNumber,
|
|
1577
|
+
endLine: lineNumber,
|
|
1578
|
+
startColumn: 1
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
function splitParameters(raw) {
|
|
1583
|
+
return String(raw ?? '')
|
|
1584
|
+
.split(',')
|
|
1585
|
+
.map((part) => part.trim())
|
|
1586
|
+
.filter(Boolean);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
function inferSourceMapMappings(input) {
|
|
1590
|
+
const semanticIndex = input.semanticIndex;
|
|
1591
|
+
const nativeAst = input.nativeAst;
|
|
1592
|
+
const nativeSource = input.nativeSource;
|
|
1593
|
+
const evidenceIds = [
|
|
1594
|
+
...(semanticIndex?.evidence ?? []).map((record) => record.id),
|
|
1595
|
+
...(input.evidence ?? []).map((record) => record.id)
|
|
1596
|
+
];
|
|
1597
|
+
const symbolsById = new Map((semanticIndex?.symbols ?? []).map((symbol) => [symbol.id, symbol]));
|
|
1598
|
+
|
|
1599
|
+
if (semanticIndex?.occurrences?.length) {
|
|
1600
|
+
return semanticIndex.occurrences
|
|
1601
|
+
.filter((occurrence) => occurrence.nativeAstNodeId || occurrence.span)
|
|
1602
|
+
.map((occurrence) => {
|
|
1603
|
+
const symbol = symbolsById.get(occurrence.symbolId);
|
|
1604
|
+
const nativeNode = occurrence.nativeAstNodeId ? nativeAst?.nodes?.[occurrence.nativeAstNodeId] : undefined;
|
|
1605
|
+
return {
|
|
1606
|
+
id: `map_${idFragment(occurrence.id)}`,
|
|
1607
|
+
nativeSourceId: nativeSource?.id,
|
|
1608
|
+
nativeAstNodeId: occurrence.nativeAstNodeId,
|
|
1609
|
+
semanticSymbolId: occurrence.symbolId,
|
|
1610
|
+
semanticOccurrenceId: occurrence.id,
|
|
1611
|
+
semanticNodeId: occurrence.semanticNodeId ?? symbol?.semanticNodeId,
|
|
1612
|
+
sourceSpan: occurrence.span ?? nativeNode?.span,
|
|
1613
|
+
evidenceIds,
|
|
1614
|
+
lossIds: lossIdsForNativeNode(input.losses ?? nativeAst?.losses ?? [], occurrence.nativeAstNodeId),
|
|
1615
|
+
precision: occurrence.span || nativeNode?.span ? 'declaration' : 'unknown'
|
|
1616
|
+
};
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
return Object.values(nativeAst?.nodes ?? {})
|
|
1621
|
+
.filter((node) => node.span)
|
|
1622
|
+
.map((node) => ({
|
|
1623
|
+
id: `map_${idFragment(node.id)}`,
|
|
1624
|
+
nativeSourceId: nativeSource?.id,
|
|
1625
|
+
nativeAstNodeId: node.id,
|
|
1626
|
+
sourceSpan: node.span,
|
|
1627
|
+
evidenceIds,
|
|
1628
|
+
lossIds: lossIdsForNativeNode(input.losses ?? nativeAst?.losses ?? [], node.id),
|
|
1629
|
+
precision: 'line'
|
|
1630
|
+
}));
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
function normalizeSourceMapMappings(mappings, context) {
|
|
1634
|
+
if (mappings === undefined || mappings === null) return [];
|
|
1635
|
+
if (!Array.isArray(mappings)) {
|
|
1636
|
+
throw new Error('Source-map mappings must be an array');
|
|
1637
|
+
}
|
|
1638
|
+
const semanticIndex = context.semanticIndex;
|
|
1639
|
+
const nativeAst = context.nativeAst;
|
|
1640
|
+
const nativeSource = context.nativeSource;
|
|
1641
|
+
const symbolsById = new Map((semanticIndex?.symbols ?? []).map((symbol) => [symbol.id, symbol]));
|
|
1642
|
+
const occurrencesById = new Map((semanticIndex?.occurrences ?? []).map((occurrence) => [occurrence.id, occurrence]));
|
|
1643
|
+
const evidenceIds = uniqueStrings([
|
|
1644
|
+
...(semanticIndex?.evidence ?? []).map((record) => record.id),
|
|
1645
|
+
...(context.evidence ?? []).map((record) => record.id),
|
|
1646
|
+
...(context.sourceMapEvidence ?? []).map((record) => record.id)
|
|
1647
|
+
]);
|
|
1648
|
+
const usedMappingIds = new Set();
|
|
1649
|
+
return mappings
|
|
1650
|
+
.map((mapping, index) => {
|
|
1651
|
+
if (!mapping || typeof mapping !== 'object') {
|
|
1652
|
+
throw new Error(`Source-map mapping ${index + 1} must be an object`);
|
|
1653
|
+
}
|
|
1654
|
+
const occurrence = mapping.semanticOccurrenceId ? occurrencesById.get(mapping.semanticOccurrenceId) : undefined;
|
|
1655
|
+
const symbol = mapping.semanticSymbolId ? symbolsById.get(mapping.semanticSymbolId) : occurrence ? symbolsById.get(occurrence.symbolId) : undefined;
|
|
1656
|
+
const nativeAstNodeId = mapping.nativeAstNodeId ?? occurrence?.nativeAstNodeId;
|
|
1657
|
+
const nativeNode = nativeAstNodeId ? nativeAst?.nodes?.[nativeAstNodeId] : undefined;
|
|
1658
|
+
const sourceSpan = mapping.sourceSpan ?? occurrence?.span ?? nativeNode?.span;
|
|
1659
|
+
const target = mapping.target ?? mapping.generatedSpan?.target ?? context.target;
|
|
1660
|
+
const generatedSpan = normalizeGeneratedSpan(mapping.generatedSpan, target, context.targetPath, context.targetHash);
|
|
1661
|
+
if (
|
|
1662
|
+
!nativeAstNodeId &&
|
|
1663
|
+
!mapping.semanticNodeId &&
|
|
1664
|
+
!mapping.semanticSymbolId &&
|
|
1665
|
+
!mapping.semanticOccurrenceId &&
|
|
1666
|
+
!sourceSpan &&
|
|
1667
|
+
!generatedSpan &&
|
|
1668
|
+
!mapping.generatedName
|
|
1669
|
+
) {
|
|
1670
|
+
throw new Error(`Source-map mapping ${index + 1} must reference a native AST node, semantic node, symbol, occurrence, source span, or generated span`);
|
|
1671
|
+
}
|
|
1672
|
+
const normalizedMapping = {
|
|
1673
|
+
...mapping,
|
|
1674
|
+
nativeSourceId: mapping.nativeSourceId ?? nativeSource?.id,
|
|
1675
|
+
nativeAstNodeId,
|
|
1676
|
+
semanticSymbolId: mapping.semanticSymbolId ?? occurrence?.symbolId,
|
|
1677
|
+
semanticOccurrenceId: mapping.semanticOccurrenceId ?? occurrence?.id,
|
|
1678
|
+
semanticNodeId: mapping.semanticNodeId ?? occurrence?.semanticNodeId ?? symbol?.semanticNodeId,
|
|
1679
|
+
sourceSpan,
|
|
1680
|
+
generatedSpan,
|
|
1681
|
+
target,
|
|
1682
|
+
evidenceIds: normalizeReferenceIds(mapping.evidenceIds, evidenceIds),
|
|
1683
|
+
lossIds: normalizeReferenceIds(mapping.lossIds, lossIdsForNativeNode(context.losses ?? nativeAst?.losses ?? [], nativeAstNodeId)),
|
|
1684
|
+
precision: normalizeSourceMapPrecision(mapping.precision, sourceSpan, generatedSpan)
|
|
1685
|
+
};
|
|
1686
|
+
return {
|
|
1687
|
+
...normalizedMapping,
|
|
1688
|
+
id: reserveUniqueId(sourceMapMappingBaseId(normalizedMapping, index), usedMappingIds)
|
|
1689
|
+
};
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
function lossIdsForNativeNode(losses, nativeAstNodeId) {
|
|
1694
|
+
if (!nativeAstNodeId) return [];
|
|
1695
|
+
return (losses ?? [])
|
|
1696
|
+
.filter((loss) => loss.nodeId === nativeAstNodeId)
|
|
1697
|
+
.map((loss) => loss.id);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
function normalizeSourceMaps(sourceMaps, context) {
|
|
1701
|
+
if (sourceMaps === undefined || sourceMaps === null) return [];
|
|
1702
|
+
if (!Array.isArray(sourceMaps)) {
|
|
1703
|
+
throw new Error('Native import sourceMaps must be an array');
|
|
1704
|
+
}
|
|
1705
|
+
const usedSourceMapIds = new Set();
|
|
1706
|
+
return sourceMaps.map((sourceMap, index) => {
|
|
1707
|
+
if (!sourceMap || typeof sourceMap !== 'object') {
|
|
1708
|
+
throw new Error(`Native import source map ${index + 1} must be an object`);
|
|
1709
|
+
}
|
|
1710
|
+
const id = reserveUniqueId(String(sourceMap.id ?? `${context.defaultSourceMapId}_${index + 1}`), usedSourceMapIds);
|
|
1711
|
+
const evidence = uniqueRecordsById([...(sourceMap.evidence ?? []), ...(context.evidence ?? [])]);
|
|
1712
|
+
const target = sourceMap.target ?? context.target;
|
|
1713
|
+
const targetPath = sourceMap.targetPath ?? context.targetPath;
|
|
1714
|
+
const targetHash = sourceMap.targetHash ?? context.targetHash;
|
|
1715
|
+
const mappings = normalizeSourceMapMappings(sourceMap.mappings ?? [], {
|
|
1716
|
+
...context,
|
|
1717
|
+
target,
|
|
1718
|
+
targetPath,
|
|
1719
|
+
targetHash,
|
|
1720
|
+
sourceMapEvidence: evidence
|
|
1721
|
+
});
|
|
1722
|
+
const normalized = createSourceMapRecord({
|
|
1723
|
+
...sourceMap,
|
|
1724
|
+
id,
|
|
1725
|
+
sourcePath: sourceMap.sourcePath ?? context.sourcePath,
|
|
1726
|
+
sourceHash: sourceMap.sourceHash ?? context.sourceHash,
|
|
1727
|
+
target,
|
|
1728
|
+
targetPath: targetPath ?? commonGeneratedTargetPath(mappings),
|
|
1729
|
+
targetHash,
|
|
1730
|
+
semanticIndexId: sourceMap.semanticIndexId ?? context.semanticIndex?.id,
|
|
1731
|
+
nativeAstId: sourceMap.nativeAstId ?? context.nativeAst?.id,
|
|
1732
|
+
nativeSourceId: sourceMap.nativeSourceId ?? context.nativeSource?.id,
|
|
1733
|
+
mappings,
|
|
1734
|
+
evidence
|
|
1735
|
+
});
|
|
1736
|
+
const issues = validateSourceMapRecord(normalized, {
|
|
1737
|
+
document: context.document,
|
|
1738
|
+
nativeSources: context.nativeSources,
|
|
1739
|
+
nativeAst: context.nativeAst,
|
|
1740
|
+
semanticIndex: context.semanticIndex,
|
|
1741
|
+
losses: context.losses,
|
|
1742
|
+
evidence: context.evidence
|
|
1743
|
+
});
|
|
1744
|
+
if (issues.length) {
|
|
1745
|
+
throw new Error(`Invalid Frontier native source map ${normalized.id}: ${issues.join('; ')}`);
|
|
1746
|
+
}
|
|
1747
|
+
return normalized;
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function normalizeGeneratedSpan(generatedSpan, target, targetPath, targetHash) {
|
|
1752
|
+
if (!generatedSpan) return generatedSpan;
|
|
1753
|
+
return {
|
|
1754
|
+
...generatedSpan,
|
|
1755
|
+
target: generatedSpan.target ?? target,
|
|
1756
|
+
targetPath: generatedSpan.targetPath ?? target?.emitPath ?? targetPath,
|
|
1757
|
+
targetHash: generatedSpan.targetHash ?? targetHash
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function normalizeSourceMapPrecision(value, sourceSpan, generatedSpan) {
|
|
1762
|
+
const explicit = value === undefined || value === null ? '' : String(value).trim();
|
|
1763
|
+
if (explicit) {
|
|
1764
|
+
const normalized = explicit.toLowerCase();
|
|
1765
|
+
if (normalized === 'exact' || normalized === 'declaration' || normalized === 'line' || normalized === 'estimated' || normalized === 'unknown') return normalized;
|
|
1766
|
+
if (normalized === 'estimate' || normalized === 'approx' || normalized === 'approximate' || normalized === 'approximated') return 'estimated';
|
|
1767
|
+
return explicit;
|
|
1768
|
+
}
|
|
1769
|
+
if (hasExactSpan(sourceSpan) && hasExactSpan(generatedSpan)) return 'exact';
|
|
1770
|
+
if (sourceSpan?.startLine && generatedSpan?.startLine) return 'line';
|
|
1771
|
+
if (sourceSpan?.startLine) return 'declaration';
|
|
1772
|
+
if (generatedSpan?.startLine) return 'line';
|
|
1773
|
+
return 'unknown';
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
function hasExactSpan(span) {
|
|
1777
|
+
return Boolean(span && (
|
|
1778
|
+
(typeof span.start === 'number' && typeof span.end === 'number') ||
|
|
1779
|
+
(
|
|
1780
|
+
typeof span.startLine === 'number' &&
|
|
1781
|
+
typeof span.startColumn === 'number' &&
|
|
1782
|
+
typeof span.endLine === 'number' &&
|
|
1783
|
+
typeof span.endColumn === 'number'
|
|
1784
|
+
)
|
|
1785
|
+
));
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
function normalizeReferenceIds(value, fallback = []) {
|
|
1789
|
+
if (value === undefined || value === null) return uniqueStrings(fallback);
|
|
1790
|
+
return uniqueStrings(Array.isArray(value) ? value : [value]);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
function sourceMapMappingBaseId(mapping, index) {
|
|
1794
|
+
const explicit = mapping.id === undefined || mapping.id === null ? '' : String(mapping.id).trim();
|
|
1795
|
+
if (explicit) return explicit;
|
|
1796
|
+
const span = mapping.sourceSpan ?? mapping.generatedSpan;
|
|
1797
|
+
const spanPart = span
|
|
1798
|
+
? `${span.path ?? span.targetPath ?? span.sourceId ?? 'span'}_${span.start ?? span.startLine ?? ''}_${span.end ?? span.endLine ?? ''}`
|
|
1799
|
+
: undefined;
|
|
1800
|
+
return `map_${idFragment([
|
|
1801
|
+
mapping.nativeAstNodeId,
|
|
1802
|
+
mapping.semanticOccurrenceId,
|
|
1803
|
+
mapping.semanticSymbolId,
|
|
1804
|
+
mapping.semanticNodeId,
|
|
1805
|
+
spanPart,
|
|
1806
|
+
index + 1
|
|
1807
|
+
].filter(Boolean).join('_'))}`;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function reserveUniqueId(baseId, usedIds) {
|
|
1811
|
+
const safeBase = String(baseId || 'id');
|
|
1812
|
+
if (!usedIds.has(safeBase)) {
|
|
1813
|
+
usedIds.add(safeBase);
|
|
1814
|
+
return safeBase;
|
|
1815
|
+
}
|
|
1816
|
+
let index = 2;
|
|
1817
|
+
while (usedIds.has(`${safeBase}_${index}`)) index += 1;
|
|
1818
|
+
const id = `${safeBase}_${index}`;
|
|
1819
|
+
usedIds.add(id);
|
|
1820
|
+
return id;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
function commonGeneratedTargetPath(mappings) {
|
|
1824
|
+
const paths = uniqueStrings((mappings ?? [])
|
|
1825
|
+
.map((mapping) => mapping.generatedSpan?.targetPath ?? mapping.target?.emitPath)
|
|
1826
|
+
.filter(Boolean));
|
|
1827
|
+
return paths.length === 1 ? paths[0] : undefined;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
function uniqueRecordsById(records) {
|
|
1831
|
+
const seen = new Set();
|
|
1832
|
+
const result = [];
|
|
1833
|
+
for (const record of records ?? []) {
|
|
1834
|
+
if (!record?.id || seen.has(record.id)) continue;
|
|
1835
|
+
seen.add(record.id);
|
|
1836
|
+
result.push(record);
|
|
1837
|
+
}
|
|
1838
|
+
return result;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
function normalizeNativeLossRecords(losses) {
|
|
1842
|
+
return (Array.isArray(losses) ? losses : [losses])
|
|
1843
|
+
.filter((loss) => loss !== undefined && loss !== null)
|
|
1844
|
+
.map((loss, index) => normalizeNativeLossRecord(loss, `loss_${index + 1}`));
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function normalizeNativeLossRecord(loss, fallbackId) {
|
|
1848
|
+
const record = typeof loss === 'string' ? { message: loss } : loss ?? {};
|
|
1849
|
+
const severity = normalizeLossSeverity(record.severity);
|
|
1850
|
+
const kind = normalizeNativeLossKind(record, severity);
|
|
1851
|
+
const category = nativeImportCategoryForLossKind(kind);
|
|
1852
|
+
return {
|
|
1853
|
+
...record,
|
|
1854
|
+
id: String(record.id ?? fallbackId),
|
|
1855
|
+
severity,
|
|
1856
|
+
kind,
|
|
1857
|
+
message: String(record.message ?? `${kind} loss during native import.`),
|
|
1858
|
+
metadata: {
|
|
1859
|
+
lossCategory: category,
|
|
1860
|
+
semanticMergeAdmission: semanticMergeAdmissionForSeverity(severity),
|
|
1861
|
+
dashboardSeverity: severity,
|
|
1862
|
+
...record.metadata
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
function normalizeLossSeverity(value) {
|
|
1868
|
+
const severity = String(value ?? 'warning').toLowerCase();
|
|
1869
|
+
if (severity === 'error') return 'error';
|
|
1870
|
+
if (severity === 'info') return 'info';
|
|
1871
|
+
return 'warning';
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
function normalizeNativeLossKind(loss, severity) {
|
|
1875
|
+
const kind = String(loss.kind ?? '').trim();
|
|
1876
|
+
if (kind) return kind;
|
|
1877
|
+
if (loss.metadata?.diagnosticId || loss.metadata?.diagnosticCode) {
|
|
1878
|
+
return severity === 'error' ? 'unsupportedSyntax' : 'parserDiagnostic';
|
|
1879
|
+
}
|
|
1880
|
+
return severity === 'error' ? 'unsupportedSyntax' : 'opaqueNative';
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
function nativeImportCategoryForLossKind(kind) {
|
|
1884
|
+
if (kind === 'declarationOnlyCoverage') return 'declarationsOnly';
|
|
1885
|
+
if (kind === 'opaqueNative') return 'opaqueBodies';
|
|
1886
|
+
if (kind === 'macroExpansion') return 'macroExpansion';
|
|
1887
|
+
if (kind === 'preprocessor' || kind === 'conditionalCompilation' || kind === 'macroHygiene') return 'preprocessor';
|
|
1888
|
+
if (kind === 'metaprogramming' || kind === 'reflection' || kind === 'dynamicDispatch' || kind === 'dynamicRuntime') return 'metaprogramming';
|
|
1889
|
+
if (kind === 'generatedCode') return 'generatedCode';
|
|
1890
|
+
if (kind === 'sourcePreservation' || kind === 'nonRoundTrippable') return 'sourcePreservation';
|
|
1891
|
+
if (kind === 'parserDiagnostic') return 'parserDiagnostics';
|
|
1892
|
+
if (kind === 'unsupportedSyntax' || kind === 'unsupportedSemantic') return 'unsupportedSyntax';
|
|
1893
|
+
if (kind === 'partialSemanticIndex') return 'partialSemanticIndex';
|
|
1894
|
+
if (kind === 'sourceMapApproximation') return 'sourceMapApproximation';
|
|
1895
|
+
return String(kind ?? 'opaqueNative');
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
function semanticMergeAdmissionForSeverity(severity) {
|
|
1899
|
+
if (severity === 'error') return 'blocked';
|
|
1900
|
+
if (severity === 'warning') return 'review';
|
|
1901
|
+
return 'disclose';
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
function nativeImportReadinessReasons(input) {
|
|
1905
|
+
if (input.failedEvidenceIds.length) {
|
|
1906
|
+
return [`Failed native import evidence prevents merge: ${input.failedEvidenceIds.join(', ')}`];
|
|
1907
|
+
}
|
|
1908
|
+
if (input.blockingLossIds.length) {
|
|
1909
|
+
return [`Native import error loss(es) block semantic merge: ${input.blockingLossIds.join(', ')}`];
|
|
1910
|
+
}
|
|
1911
|
+
if (input.reviewLossIds.length) {
|
|
1912
|
+
return [`Native import warning loss(es) require review: ${input.reviewLossIds.join(', ')}`];
|
|
1913
|
+
}
|
|
1914
|
+
if (input.informationalLossIds.length) {
|
|
1915
|
+
return [`Native import recorded informational loss(es): ${input.informationalLossIds.join(', ')}`];
|
|
1916
|
+
}
|
|
1917
|
+
if (input.exactAst) return ['Native import supplied exact AST coverage with no recorded loss.'];
|
|
1918
|
+
return ['Native import has no recorded loss.'];
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
function attachNativeImportLossSummary(evidence, lossSummary) {
|
|
1922
|
+
return (evidence ?? []).map((record) => ({
|
|
1923
|
+
...record,
|
|
1924
|
+
metadata: {
|
|
1925
|
+
...record.metadata,
|
|
1926
|
+
nativeImportLossSummary: lossSummary,
|
|
1927
|
+
semanticMergeReadiness: lossSummary.semanticMergeReadiness,
|
|
1928
|
+
lossCategories: lossSummary.categories
|
|
1929
|
+
}
|
|
1930
|
+
}));
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
function withNativeImportReadiness(importResult, lossSummary) {
|
|
1934
|
+
const mergeCandidates = (importResult.mergeCandidates ?? []).map((candidate) => {
|
|
1935
|
+
const readiness = maxSemanticMergeReadiness(candidate.readiness, lossSummary.semanticMergeReadiness);
|
|
1936
|
+
return {
|
|
1937
|
+
...candidate,
|
|
1938
|
+
readiness,
|
|
1939
|
+
reasons: uniqueStrings([
|
|
1940
|
+
...(candidate.reasons ?? []),
|
|
1941
|
+
...lossSummary.readinessReasons
|
|
1942
|
+
]),
|
|
1943
|
+
metadata: {
|
|
1944
|
+
...candidate.metadata,
|
|
1945
|
+
nativeImportLossSummary: lossSummary,
|
|
1946
|
+
severityReadiness: lossSummary.semanticMergeReadiness,
|
|
1947
|
+
finalReadiness: readiness,
|
|
1948
|
+
lossCategories: lossSummary.categories,
|
|
1949
|
+
lossSeverityCounts: lossSummary.bySeverity,
|
|
1950
|
+
lossKindCounts: lossSummary.byKind
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
});
|
|
1954
|
+
return {
|
|
1955
|
+
...importResult,
|
|
1956
|
+
mergeCandidates,
|
|
1957
|
+
metadata: {
|
|
1958
|
+
...importResult.metadata,
|
|
1959
|
+
nativeImportLossSummary: lossSummary,
|
|
1960
|
+
semanticMergeReadiness: mergeCandidates[0]?.readiness ?? lossSummary.semanticMergeReadiness,
|
|
1961
|
+
lossCategories: lossSummary.categories,
|
|
1962
|
+
lossSeverityCounts: lossSummary.bySeverity,
|
|
1963
|
+
lossKindCounts: lossSummary.byKind
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
function maxSemanticMergeReadiness(left, right) {
|
|
1969
|
+
const leftRank = semanticMergeReadinessRank[left] ?? semanticMergeReadinessRank['needs-review'];
|
|
1970
|
+
const rightRank = semanticMergeReadinessRank[right] ?? semanticMergeReadinessRank['needs-review'];
|
|
1971
|
+
return leftRank >= rightRank ? left : right;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
export function createUniversalAstFromDocument(document, input = {}) {
|
|
1975
|
+
return createUniversalAstEnvelope({
|
|
1976
|
+
id: input.id ?? `universal_ast_${idFragment(document.id)}`,
|
|
1977
|
+
document,
|
|
1978
|
+
semanticIndex: input.semanticIndex,
|
|
1979
|
+
sourceMaps: input.sourceMaps ?? [],
|
|
1980
|
+
evidence: input.evidence ?? [],
|
|
1981
|
+
metadata: input.metadata
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
export function readUniversalAstJson(source) {
|
|
701
1986
|
const envelope = JSON.parse(source);
|
|
702
1987
|
const issues = validateUniversalAstEnvelope(envelope);
|
|
703
1988
|
if (issues.length > 0) {
|
|
@@ -718,6 +2003,536 @@ export function emitForTarget(document, target = 'typescript', options = {}) {
|
|
|
718
2003
|
return renderTargetAst(projectFrontierAst(document, target, options), target);
|
|
719
2004
|
}
|
|
720
2005
|
|
|
2006
|
+
function createJavaScriptSyntaxImporterAdapter(options) {
|
|
2007
|
+
return {
|
|
2008
|
+
id: options.id,
|
|
2009
|
+
language: options.language,
|
|
2010
|
+
parser: options.parser,
|
|
2011
|
+
version: options.version,
|
|
2012
|
+
capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
|
|
2013
|
+
supportedExtensions: options.supportedExtensions,
|
|
2014
|
+
diagnostics: options.diagnostics,
|
|
2015
|
+
parse(input) {
|
|
2016
|
+
const ast = input.options?.ast ?? input.options?.nativeAst ?? options.ast ?? parseJavaScriptSyntax(input, options);
|
|
2017
|
+
if (!ast) {
|
|
2018
|
+
return missingInjectedParserResult(input, {
|
|
2019
|
+
parser: options.parser,
|
|
2020
|
+
adapterId: options.id,
|
|
2021
|
+
message: `${options.id} requires an injected parse function, parserModule.parse, ast, or adapterOptions.ast.`
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
const parseDiagnostics = normalizeParserErrors(ast.errors, input, options);
|
|
2025
|
+
return createNativeImportFromSyntaxAst(ast, input, {
|
|
2026
|
+
parser: options.parser,
|
|
2027
|
+
astFormat: options.astFormat,
|
|
2028
|
+
maxNodes: options.maxNodes,
|
|
2029
|
+
diagnostics: parseDiagnostics
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
function parseJavaScriptSyntax(input, options) {
|
|
2036
|
+
const parse = options.parse ?? options.parserModule?.parse ?? options.babelParser?.parse;
|
|
2037
|
+
if (typeof parse !== 'function') return undefined;
|
|
2038
|
+
const parserOptions = {
|
|
2039
|
+
sourceFilename: input.sourcePath,
|
|
2040
|
+
...(options.defaultParserOptions ?? {}),
|
|
2041
|
+
...(typeof options.parserOptions === 'function'
|
|
2042
|
+
? options.parserOptions(input)
|
|
2043
|
+
: options.parserOptions ?? {}),
|
|
2044
|
+
...(input.options?.parserOptions ?? {})
|
|
2045
|
+
};
|
|
2046
|
+
return parse(input.sourceText, parserOptions);
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
function createTypeScriptSourceFile(ts, input, options) {
|
|
2050
|
+
if (typeof options.createSourceFile === 'function') return options.createSourceFile(input, ts);
|
|
2051
|
+
if (!ts || typeof ts.createSourceFile !== 'function') return undefined;
|
|
2052
|
+
const scriptTarget = options.scriptTarget ?? ts.ScriptTarget?.Latest ?? ts.ScriptTarget?.ESNext ?? 99;
|
|
2053
|
+
const scriptKind = options.scriptKind ?? inferTypeScriptScriptKind(ts, input);
|
|
2054
|
+
return ts.createSourceFile(input.sourcePath ?? 'frontier-input.ts', input.sourceText, scriptTarget, true, scriptKind);
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function inferTypeScriptScriptKind(ts, input) {
|
|
2058
|
+
const path = String(input.sourcePath ?? '').toLowerCase();
|
|
2059
|
+
if (path.endsWith('.tsx')) return ts.ScriptKind?.TSX;
|
|
2060
|
+
if (path.endsWith('.jsx')) return ts.ScriptKind?.JSX;
|
|
2061
|
+
if (path.endsWith('.js') || path.endsWith('.mjs') || path.endsWith('.cjs')) return ts.ScriptKind?.JS;
|
|
2062
|
+
return ts.ScriptKind?.TS;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
function parseTreeSitterSource(input, options) {
|
|
2066
|
+
const parser = options.parserInstance ?? options.treeSitterParser ?? options.parser;
|
|
2067
|
+
if (parser && typeof parser.parse === 'function') return parser.parse(input.sourceText);
|
|
2068
|
+
if (typeof options.parse === 'function') return options.parse(input);
|
|
2069
|
+
return undefined;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
function createNativeImportFromSyntaxAst(ast, input, options) {
|
|
2073
|
+
const root = normalizeSyntaxAstRoot(ast, options.astFormat);
|
|
2074
|
+
if (!root) {
|
|
2075
|
+
return missingInjectedParserResult(input, {
|
|
2076
|
+
parser: options.parser,
|
|
2077
|
+
adapterId: input.adapterId,
|
|
2078
|
+
message: 'Injected AST did not contain an object root node.'
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
const context = createAstNormalizationContext(input, options);
|
|
2082
|
+
visitSyntaxAstNode(root, context, 'root');
|
|
2083
|
+
if (context.truncated) {
|
|
2084
|
+
context.losses.push(truncatedAstLoss(input, context, options));
|
|
2085
|
+
}
|
|
2086
|
+
const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
|
|
2087
|
+
return {
|
|
2088
|
+
rootId: context.rootId,
|
|
2089
|
+
nodes: context.nodes,
|
|
2090
|
+
semanticIndex: semantic.semanticIndex,
|
|
2091
|
+
mappings: semantic.mappings,
|
|
2092
|
+
losses: mergeNativeLosses(context.losses, options.diagnostics?.map((diagnostic, index) => adapterDiagnosticToLoss(diagnostic, index, {
|
|
2093
|
+
id: input.adapterId,
|
|
2094
|
+
version: input.adapterVersion
|
|
2095
|
+
}, input)) ?? []),
|
|
2096
|
+
evidence: semantic.evidence,
|
|
2097
|
+
diagnostics: options.diagnostics,
|
|
2098
|
+
metadata: {
|
|
2099
|
+
astFormat: options.astFormat,
|
|
2100
|
+
parser: options.parser,
|
|
2101
|
+
normalizedNodeCount: Object.keys(context.nodes).length,
|
|
2102
|
+
declarationCount: context.declarations.length,
|
|
2103
|
+
truncated: context.truncated
|
|
2104
|
+
}
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
function createNativeImportFromTypeScriptAst(sourceFile, input, options) {
|
|
2109
|
+
const context = createAstNormalizationContext(input, options);
|
|
2110
|
+
visitTypeScriptAstNode(sourceFile, sourceFile, context, 'root', options.ts);
|
|
2111
|
+
if (context.truncated) {
|
|
2112
|
+
context.losses.push(truncatedAstLoss(input, context, options));
|
|
2113
|
+
}
|
|
2114
|
+
const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
|
|
2115
|
+
return {
|
|
2116
|
+
rootId: context.rootId,
|
|
2117
|
+
nodes: context.nodes,
|
|
2118
|
+
semanticIndex: semantic.semanticIndex,
|
|
2119
|
+
mappings: semantic.mappings,
|
|
2120
|
+
losses: context.losses,
|
|
2121
|
+
evidence: semantic.evidence,
|
|
2122
|
+
metadata: {
|
|
2123
|
+
astFormat: options.astFormat,
|
|
2124
|
+
parser: options.parser,
|
|
2125
|
+
normalizedNodeCount: Object.keys(context.nodes).length,
|
|
2126
|
+
declarationCount: context.declarations.length,
|
|
2127
|
+
truncated: context.truncated
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
function createNativeImportFromTreeSitter(root, input, options) {
|
|
2133
|
+
const context = createAstNormalizationContext(input, options);
|
|
2134
|
+
visitTreeSitterNode(root, context, 'root');
|
|
2135
|
+
if (context.truncated) {
|
|
2136
|
+
context.losses.push(truncatedAstLoss(input, context, options));
|
|
2137
|
+
}
|
|
2138
|
+
const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
|
|
2139
|
+
return {
|
|
2140
|
+
rootId: context.rootId,
|
|
2141
|
+
nodes: context.nodes,
|
|
2142
|
+
semanticIndex: semantic.semanticIndex,
|
|
2143
|
+
mappings: semantic.mappings,
|
|
2144
|
+
losses: context.losses,
|
|
2145
|
+
evidence: semantic.evidence,
|
|
2146
|
+
metadata: {
|
|
2147
|
+
astFormat: options.astFormat,
|
|
2148
|
+
parser: options.parser,
|
|
2149
|
+
normalizedNodeCount: Object.keys(context.nodes).length,
|
|
2150
|
+
declarationCount: context.declarations.length,
|
|
2151
|
+
truncated: context.truncated
|
|
2152
|
+
}
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
function createAstNormalizationContext(input, options) {
|
|
2157
|
+
return {
|
|
2158
|
+
input,
|
|
2159
|
+
options,
|
|
2160
|
+
maxNodes: Number.isFinite(options.maxNodes) ? Math.max(1, options.maxNodes) : 5000,
|
|
2161
|
+
counter: 0,
|
|
2162
|
+
objectIds: new WeakMap(),
|
|
2163
|
+
nodes: {},
|
|
2164
|
+
declarations: [],
|
|
2165
|
+
losses: [],
|
|
2166
|
+
rootId: undefined,
|
|
2167
|
+
truncated: false
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
function normalizeSyntaxAstRoot(ast, astFormat) {
|
|
2172
|
+
if (!ast || typeof ast !== 'object') return undefined;
|
|
2173
|
+
if (astFormat === 'babel' && ast.program && typeof ast.program === 'object') return ast.program;
|
|
2174
|
+
return ast;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
function visitSyntaxAstNode(node, context, propertyPath) {
|
|
2178
|
+
if (!isSyntaxAstNode(node) || context.truncated) return undefined;
|
|
2179
|
+
if (context.objectIds.has(node)) return context.objectIds.get(node);
|
|
2180
|
+
if (context.counter >= context.maxNodes) {
|
|
2181
|
+
context.truncated = true;
|
|
2182
|
+
return undefined;
|
|
2183
|
+
}
|
|
2184
|
+
const id = nativeNodeId(context, node.type ?? node.kind ?? 'Node', node.loc, propertyPath);
|
|
2185
|
+
context.objectIds.set(node, id);
|
|
2186
|
+
if (!context.rootId) context.rootId = id;
|
|
2187
|
+
const children = [];
|
|
2188
|
+
const fields = primitiveSyntaxFields(node);
|
|
2189
|
+
for (const [key, value] of Object.entries(node)) {
|
|
2190
|
+
if (ignoredSyntaxField(key)) continue;
|
|
2191
|
+
if (Array.isArray(value)) {
|
|
2192
|
+
value.forEach((entry, index) => {
|
|
2193
|
+
const childId = visitSyntaxAstNode(entry, context, `${propertyPath}.${key}[${index}]`);
|
|
2194
|
+
if (childId) children.push(childId);
|
|
2195
|
+
});
|
|
2196
|
+
} else {
|
|
2197
|
+
const childId = visitSyntaxAstNode(value, context, `${propertyPath}.${key}`);
|
|
2198
|
+
if (childId) children.push(childId);
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
const declaration = syntaxDeclaration(node, id, context.input, context.options);
|
|
2202
|
+
const nativeNode = {
|
|
2203
|
+
id,
|
|
2204
|
+
kind: String(node.type ?? node.kind ?? 'Node'),
|
|
2205
|
+
languageKind: `${context.input.language}.${node.type ?? node.kind ?? 'Node'}`,
|
|
2206
|
+
span: spanFromLoc(node.loc, context.input),
|
|
2207
|
+
value: declaration?.name ?? literalSyntaxValue(node),
|
|
2208
|
+
fields,
|
|
2209
|
+
children,
|
|
2210
|
+
metadata: {
|
|
2211
|
+
astFormat: context.options.astFormat,
|
|
2212
|
+
propertyPath,
|
|
2213
|
+
start: numberOrUndefined(node.start),
|
|
2214
|
+
end: numberOrUndefined(node.end),
|
|
2215
|
+
range: Array.isArray(node.range) ? node.range.slice(0, 2) : undefined
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
context.nodes[id] = nativeNode;
|
|
2219
|
+
if (declaration) context.declarations.push({ ...declaration, nativeNode });
|
|
2220
|
+
return id;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
function visitTypeScriptAstNode(node, sourceFile, context, propertyPath, ts) {
|
|
2224
|
+
if (!node || typeof node !== 'object' || context.truncated) return undefined;
|
|
2225
|
+
if (context.objectIds.has(node)) return context.objectIds.get(node);
|
|
2226
|
+
if (context.counter >= context.maxNodes) {
|
|
2227
|
+
context.truncated = true;
|
|
2228
|
+
return undefined;
|
|
2229
|
+
}
|
|
2230
|
+
const kind = typeScriptKindName(node, ts);
|
|
2231
|
+
const span = spanFromTypeScriptNode(node, sourceFile);
|
|
2232
|
+
const id = nativeNodeId(context, kind, { start: { line: span?.startLine, column: span?.startColumn } }, propertyPath);
|
|
2233
|
+
context.objectIds.set(node, id);
|
|
2234
|
+
if (!context.rootId) context.rootId = id;
|
|
2235
|
+
const children = [];
|
|
2236
|
+
const visitChild = (child) => {
|
|
2237
|
+
const childId = visitTypeScriptAstNode(child, sourceFile, context, `${propertyPath}.${children.length}`, ts);
|
|
2238
|
+
if (childId) children.push(childId);
|
|
2239
|
+
};
|
|
2240
|
+
if (ts && typeof ts.forEachChild === 'function') {
|
|
2241
|
+
ts.forEachChild(node, visitChild);
|
|
2242
|
+
} else if (typeof node.forEachChild === 'function') {
|
|
2243
|
+
node.forEachChild(visitChild);
|
|
2244
|
+
} else if (Array.isArray(node.children)) {
|
|
2245
|
+
node.children.forEach(visitChild);
|
|
2246
|
+
}
|
|
2247
|
+
const declaration = typeScriptDeclaration(node, kind, id, context.input, context.options);
|
|
2248
|
+
const nativeNode = {
|
|
2249
|
+
id,
|
|
2250
|
+
kind,
|
|
2251
|
+
languageKind: `${context.input.language}.${kind}`,
|
|
2252
|
+
span,
|
|
2253
|
+
value: declaration?.name ?? typeScriptNodeValue(node),
|
|
2254
|
+
fields: primitiveTypeScriptFields(node, kind),
|
|
2255
|
+
children,
|
|
2256
|
+
metadata: {
|
|
2257
|
+
astFormat: context.options.astFormat,
|
|
2258
|
+
propertyPath,
|
|
2259
|
+
pos: numberOrUndefined(node.pos),
|
|
2260
|
+
end: numberOrUndefined(node.end)
|
|
2261
|
+
}
|
|
2262
|
+
};
|
|
2263
|
+
context.nodes[id] = nativeNode;
|
|
2264
|
+
if (declaration) context.declarations.push({ ...declaration, nativeNode });
|
|
2265
|
+
return id;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
function visitTreeSitterNode(node, context, propertyPath) {
|
|
2269
|
+
if (!node || typeof node !== 'object' || context.truncated) return undefined;
|
|
2270
|
+
if (context.objectIds.has(node)) return context.objectIds.get(node);
|
|
2271
|
+
if (context.counter >= context.maxNodes) {
|
|
2272
|
+
context.truncated = true;
|
|
2273
|
+
return undefined;
|
|
2274
|
+
}
|
|
2275
|
+
const kind = String(node.type ?? node.kind ?? 'node');
|
|
2276
|
+
const span = spanFromTreeSitterNode(node, context.input);
|
|
2277
|
+
const id = nativeNodeId(context, kind, { start: { line: span?.startLine, column: span?.startColumn } }, propertyPath);
|
|
2278
|
+
context.objectIds.set(node, id);
|
|
2279
|
+
if (!context.rootId) context.rootId = id;
|
|
2280
|
+
const children = [];
|
|
2281
|
+
const rawChildren = Array.isArray(node.namedChildren)
|
|
2282
|
+
? node.namedChildren
|
|
2283
|
+
: Array.isArray(node.children)
|
|
2284
|
+
? node.children
|
|
2285
|
+
: [];
|
|
2286
|
+
rawChildren.forEach((child, index) => {
|
|
2287
|
+
const childId = visitTreeSitterNode(child, context, `${propertyPath}.children[${index}]`);
|
|
2288
|
+
if (childId) children.push(childId);
|
|
2289
|
+
});
|
|
2290
|
+
const declaration = treeSitterDeclaration(node, kind, id, context.input, context.options);
|
|
2291
|
+
const nativeNode = {
|
|
2292
|
+
id,
|
|
2293
|
+
kind,
|
|
2294
|
+
languageKind: `${context.input.language}.${kind}`,
|
|
2295
|
+
span,
|
|
2296
|
+
value: declaration?.name ?? shortNodeText(node),
|
|
2297
|
+
fields: {
|
|
2298
|
+
named: Boolean(node.isNamed ?? node.named),
|
|
2299
|
+
missing: Boolean(node.isMissing),
|
|
2300
|
+
error: Boolean(node.hasError || kind === 'ERROR')
|
|
2301
|
+
},
|
|
2302
|
+
children,
|
|
2303
|
+
metadata: {
|
|
2304
|
+
astFormat: context.options.astFormat,
|
|
2305
|
+
propertyPath,
|
|
2306
|
+
startIndex: numberOrUndefined(node.startIndex),
|
|
2307
|
+
endIndex: numberOrUndefined(node.endIndex)
|
|
2308
|
+
}
|
|
2309
|
+
};
|
|
2310
|
+
context.nodes[id] = nativeNode;
|
|
2311
|
+
if (declaration) context.declarations.push({ ...declaration, nativeNode });
|
|
2312
|
+
if (node.hasError || kind === 'ERROR') {
|
|
2313
|
+
context.losses.push({
|
|
2314
|
+
id: `loss_${idFragment(id)}_tree_sitter_error`,
|
|
2315
|
+
severity: 'error',
|
|
2316
|
+
phase: 'parse',
|
|
2317
|
+
sourceFormat: context.input.language,
|
|
2318
|
+
kind: 'unsupportedSyntax',
|
|
2319
|
+
message: 'Tree-sitter reported a parse error node.',
|
|
2320
|
+
span,
|
|
2321
|
+
nodeId: id
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
return id;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
function semanticIndexFromNativeDeclarations(declarations, input, options) {
|
|
2328
|
+
const documentId = `doc_${idFragment(input.sourcePath ?? input.language)}_${idFragment(input.sourceHash)}`;
|
|
2329
|
+
const evidenceId = `evidence_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}_import`;
|
|
2330
|
+
const symbols = [];
|
|
2331
|
+
const occurrences = [];
|
|
2332
|
+
const relations = [];
|
|
2333
|
+
const facts = [];
|
|
2334
|
+
const mappings = [];
|
|
2335
|
+
for (const declaration of declarations) {
|
|
2336
|
+
const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${idFragment(declaration.name)}`;
|
|
2337
|
+
const occurrenceId = `occ_${idFragment(declaration.nativeNode.id)}_${declaration.role ?? 'definition'}`;
|
|
2338
|
+
symbols.push({
|
|
2339
|
+
id: symbolId,
|
|
2340
|
+
scheme: 'frontier',
|
|
2341
|
+
name: declaration.name,
|
|
2342
|
+
kind: declaration.symbolKind,
|
|
2343
|
+
language: input.language,
|
|
2344
|
+
nativeAstNodeId: declaration.nativeNode.id,
|
|
2345
|
+
signatureHash: hashSemanticValue([input.language, declaration.nativeNode.kind, declaration.name, declaration.nativeNode.fields ?? {}]),
|
|
2346
|
+
definitionSpan: declaration.nativeNode.span
|
|
2347
|
+
});
|
|
2348
|
+
occurrences.push({
|
|
2349
|
+
id: occurrenceId,
|
|
2350
|
+
documentId,
|
|
2351
|
+
symbolId,
|
|
2352
|
+
role: declaration.role ?? 'definition',
|
|
2353
|
+
span: declaration.nativeNode.span,
|
|
2354
|
+
nativeAstNodeId: declaration.nativeNode.id
|
|
2355
|
+
});
|
|
2356
|
+
relations.push({
|
|
2357
|
+
id: `rel_${idFragment(documentId)}_${idFragment(declaration.nativeNode.id)}`,
|
|
2358
|
+
sourceId: documentId,
|
|
2359
|
+
predicate: relationPredicateForDeclaration(declaration),
|
|
2360
|
+
targetId: symbolId
|
|
2361
|
+
});
|
|
2362
|
+
facts.push({
|
|
2363
|
+
id: `fact_${idFragment(declaration.nativeNode.id)}_kind`,
|
|
2364
|
+
predicate: 'nativeKind',
|
|
2365
|
+
subjectId: symbolId,
|
|
2366
|
+
value: declaration.nativeNode.languageKind
|
|
2367
|
+
});
|
|
2368
|
+
mappings.push({
|
|
2369
|
+
id: `map_${idFragment(declaration.nativeNode.id)}`,
|
|
2370
|
+
nativeAstNodeId: declaration.nativeNode.id,
|
|
2371
|
+
semanticSymbolId: symbolId,
|
|
2372
|
+
semanticOccurrenceId: occurrenceId,
|
|
2373
|
+
sourceSpan: declaration.nativeNode.span,
|
|
2374
|
+
evidenceIds: [evidenceId],
|
|
2375
|
+
lossIds: [],
|
|
2376
|
+
precision: declaration.nativeNode.span ? 'declaration' : 'unknown'
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
const evidence = [{
|
|
2380
|
+
id: evidenceId,
|
|
2381
|
+
kind: 'import',
|
|
2382
|
+
status: 'passed',
|
|
2383
|
+
path: input.sourcePath,
|
|
2384
|
+
summary: `Normalized ${options.astFormat ?? options.parser} native AST with ${declarations.length} declaration(s).`,
|
|
2385
|
+
metadata: {
|
|
2386
|
+
parser: options.parser,
|
|
2387
|
+
astFormat: options.astFormat,
|
|
2388
|
+
language: input.language,
|
|
2389
|
+
sourceHash: input.sourceHash
|
|
2390
|
+
}
|
|
2391
|
+
}];
|
|
2392
|
+
return {
|
|
2393
|
+
semanticIndex: createSemanticIndexRecord({
|
|
2394
|
+
id: `index_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}`,
|
|
2395
|
+
documents: [{
|
|
2396
|
+
id: documentId,
|
|
2397
|
+
path: input.sourcePath ?? `${input.language}:memory`,
|
|
2398
|
+
language: input.language,
|
|
2399
|
+
sourceHash: input.sourceHash
|
|
2400
|
+
}],
|
|
2401
|
+
symbols,
|
|
2402
|
+
occurrences,
|
|
2403
|
+
relations,
|
|
2404
|
+
facts,
|
|
2405
|
+
evidence,
|
|
2406
|
+
metadata: {
|
|
2407
|
+
parser: options.parser,
|
|
2408
|
+
astFormat: options.astFormat,
|
|
2409
|
+
coverage: 'native-ast-declarations'
|
|
2410
|
+
}
|
|
2411
|
+
}),
|
|
2412
|
+
mappings,
|
|
2413
|
+
evidence
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
function createNativeProjectImportResult(input, imports) {
|
|
2418
|
+
const idPart = idFragment(input.id ?? input.projectRoot ?? 'native_project');
|
|
2419
|
+
const nodes = {};
|
|
2420
|
+
const rootIds = [];
|
|
2421
|
+
const semanticIndex = mergeSemanticIndexes(imports, input, idPart);
|
|
2422
|
+
const nativeSources = [];
|
|
2423
|
+
const sourceMaps = [];
|
|
2424
|
+
const losses = [];
|
|
2425
|
+
const evidence = [];
|
|
2426
|
+
const mergeCandidates = [];
|
|
2427
|
+
const operations = [];
|
|
2428
|
+
for (const result of imports) {
|
|
2429
|
+
for (const node of Object.values(result.document?.nodes ?? {})) {
|
|
2430
|
+
nodes[node.id] = node;
|
|
2431
|
+
}
|
|
2432
|
+
rootIds.push(...(result.document?.rootIds ?? []));
|
|
2433
|
+
if (result.nativeSource) nativeSources.push(result.nativeSource);
|
|
2434
|
+
sourceMaps.push(...(result.sourceMaps ?? []));
|
|
2435
|
+
losses.push(...(result.losses ?? []));
|
|
2436
|
+
evidence.push(...(result.evidence ?? []));
|
|
2437
|
+
mergeCandidates.push(...(result.mergeCandidates ?? []));
|
|
2438
|
+
operations.push(...(result.patch?.operations ?? []));
|
|
2439
|
+
}
|
|
2440
|
+
const document = createDocument({
|
|
2441
|
+
id: input.documentId ?? `document_${idPart}`,
|
|
2442
|
+
name: input.documentName ?? input.name ?? 'NativeProject',
|
|
2443
|
+
nodes: Object.values(nodes),
|
|
2444
|
+
rootIds: uniqueStrings(rootIds),
|
|
2445
|
+
metadata: {
|
|
2446
|
+
sourceLanguage: input.language ?? 'mixed',
|
|
2447
|
+
semanticStatus: losses.some((loss) => loss.severity === 'error') ? 'partial' : 'mapped',
|
|
2448
|
+
projectRoot: input.projectRoot,
|
|
2449
|
+
sourceCount: imports.length,
|
|
2450
|
+
...input.documentMetadata
|
|
2451
|
+
}
|
|
2452
|
+
});
|
|
2453
|
+
const universalAst = createUniversalAstEnvelope({
|
|
2454
|
+
id: input.universalAstId ?? `universal_ast_${idPart}`,
|
|
2455
|
+
document,
|
|
2456
|
+
nativeSources,
|
|
2457
|
+
semanticIndex,
|
|
2458
|
+
sourceMaps,
|
|
2459
|
+
losses: uniqueByLossId(losses),
|
|
2460
|
+
evidence: uniqueByEvidenceId(evidence),
|
|
2461
|
+
metadata: {
|
|
2462
|
+
sourceLanguage: input.language ?? 'mixed',
|
|
2463
|
+
projectRoot: input.projectRoot,
|
|
2464
|
+
sourceCount: imports.length,
|
|
2465
|
+
...input.universalAstMetadata
|
|
2466
|
+
}
|
|
2467
|
+
});
|
|
2468
|
+
const patch = createPatch({
|
|
2469
|
+
id: input.patchId ?? `patch_${idPart}_project_import`,
|
|
2470
|
+
author: input.author ?? '@shapeshift-labs/frontier-lang-compiler/importNativeProject',
|
|
2471
|
+
risk: losses.some((loss) => loss.severity === 'error') ? 'high' : losses.some((loss) => loss.severity === 'warning') ? 'medium' : 'low',
|
|
2472
|
+
operations,
|
|
2473
|
+
evidence: uniqueByEvidenceId(evidence),
|
|
2474
|
+
metadata: {
|
|
2475
|
+
semanticIndexId: semanticIndex?.id,
|
|
2476
|
+
universalAstId: universalAst.id,
|
|
2477
|
+
sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
|
|
2478
|
+
sourceCount: imports.length
|
|
2479
|
+
}
|
|
2480
|
+
});
|
|
2481
|
+
return {
|
|
2482
|
+
kind: 'frontier.lang.projectImportResult',
|
|
2483
|
+
version: 1,
|
|
2484
|
+
id: input.id ?? `project_import_${idPart}`,
|
|
2485
|
+
language: input.language ?? 'mixed',
|
|
2486
|
+
projectRoot: input.projectRoot,
|
|
2487
|
+
imports,
|
|
2488
|
+
document,
|
|
2489
|
+
patch,
|
|
2490
|
+
nativeSources,
|
|
2491
|
+
semanticIndex,
|
|
2492
|
+
universalAst,
|
|
2493
|
+
sourceMaps,
|
|
2494
|
+
losses: uniqueByLossId(losses),
|
|
2495
|
+
evidence: uniqueByEvidenceId(evidence),
|
|
2496
|
+
mergeCandidates,
|
|
2497
|
+
metadata: {
|
|
2498
|
+
sourceCount: imports.length,
|
|
2499
|
+
sourcePaths: imports.map((result) => result.sourcePath).filter(Boolean),
|
|
2500
|
+
...input.metadata
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
function mergeSemanticIndexes(imports, input, idPart) {
|
|
2506
|
+
const indexes = imports.map((result) => result.semanticIndex ?? result.universalAst?.semanticIndex).filter(Boolean);
|
|
2507
|
+
if (!indexes.length) return undefined;
|
|
2508
|
+
return createSemanticIndexRecord({
|
|
2509
|
+
id: input.semanticIndexId ?? `index_${idPart}_project`,
|
|
2510
|
+
documents: indexes.flatMap((index) => index.documents ?? []),
|
|
2511
|
+
symbols: indexes.flatMap((index) => index.symbols ?? []),
|
|
2512
|
+
occurrences: indexes.flatMap((index) => index.occurrences ?? []),
|
|
2513
|
+
relations: indexes.flatMap((index) => index.relations ?? []),
|
|
2514
|
+
facts: indexes.flatMap((index) => index.facts ?? []),
|
|
2515
|
+
evidence: indexes.flatMap((index) => index.evidence ?? []),
|
|
2516
|
+
metadata: {
|
|
2517
|
+
projectRoot: input.projectRoot,
|
|
2518
|
+
sourceCount: imports.length,
|
|
2519
|
+
mergedIndexCount: indexes.length
|
|
2520
|
+
}
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
function resolveNativeProjectAdapter(source, adapters, input) {
|
|
2525
|
+
if (typeof input.adapterResolver === 'function') return input.adapterResolver(source, adapters);
|
|
2526
|
+
const language = source.language;
|
|
2527
|
+
const sourcePath = source.sourcePath ?? '';
|
|
2528
|
+
return adapters.find((adapter) => {
|
|
2529
|
+
if (source.adapter && adapter.id === source.adapter) return true;
|
|
2530
|
+
if (language && adapter.language !== language) return false;
|
|
2531
|
+
const extensions = adapter.supportedExtensions ?? [];
|
|
2532
|
+
return !extensions.length || extensions.some((extension) => sourcePath.toLowerCase().endsWith(extension.toLowerCase()));
|
|
2533
|
+
});
|
|
2534
|
+
}
|
|
2535
|
+
|
|
721
2536
|
function normalizeNativeImporterAdapter(adapter) {
|
|
722
2537
|
if (!adapter || typeof adapter !== 'object') {
|
|
723
2538
|
throw new Error('Native importer adapter must be an object');
|
|
@@ -859,6 +2674,336 @@ function serializableDiagnostic(diagnostic) {
|
|
|
859
2674
|
};
|
|
860
2675
|
}
|
|
861
2676
|
|
|
2677
|
+
function missingInjectedParserResult(input, details) {
|
|
2678
|
+
const rootId = `native_${idFragment(details.adapterId ?? details.parser)}_missing_parser`;
|
|
2679
|
+
const diagnostic = {
|
|
2680
|
+
severity: 'error',
|
|
2681
|
+
code: 'adapter.parser.missing',
|
|
2682
|
+
phase: 'parse',
|
|
2683
|
+
kind: 'unsupportedSyntax',
|
|
2684
|
+
message: details.message,
|
|
2685
|
+
path: input.sourcePath,
|
|
2686
|
+
metadata: {
|
|
2687
|
+
adapterId: details.adapterId,
|
|
2688
|
+
parser: details.parser
|
|
2689
|
+
}
|
|
2690
|
+
};
|
|
2691
|
+
return {
|
|
2692
|
+
rootId,
|
|
2693
|
+
nodes: {
|
|
2694
|
+
[rootId]: {
|
|
2695
|
+
id: rootId,
|
|
2696
|
+
kind: 'MissingInjectedParser',
|
|
2697
|
+
languageKind: `${input.language}.missingInjectedParser`,
|
|
2698
|
+
value: details.parser,
|
|
2699
|
+
metadata: {
|
|
2700
|
+
adapterId: details.adapterId,
|
|
2701
|
+
parser: details.parser,
|
|
2702
|
+
reason: 'missing-injected-parser'
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
},
|
|
2706
|
+
diagnostics: [diagnostic],
|
|
2707
|
+
losses: [{
|
|
2708
|
+
id: `loss_${idFragment(rootId)}`,
|
|
2709
|
+
severity: 'error',
|
|
2710
|
+
phase: 'parse',
|
|
2711
|
+
sourceFormat: input.language,
|
|
2712
|
+
kind: 'unsupportedSyntax',
|
|
2713
|
+
message: details.message,
|
|
2714
|
+
nodeId: rootId,
|
|
2715
|
+
metadata: {
|
|
2716
|
+
adapterId: details.adapterId,
|
|
2717
|
+
parser: details.parser
|
|
2718
|
+
}
|
|
2719
|
+
}],
|
|
2720
|
+
metadata: {
|
|
2721
|
+
parser: details.parser,
|
|
2722
|
+
adapterId: details.adapterId,
|
|
2723
|
+
missingInjectedParser: true
|
|
2724
|
+
}
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
function normalizeParserErrors(errors, input, options) {
|
|
2729
|
+
return (errors ?? []).map((error, index) => ({
|
|
2730
|
+
id: `diagnostic_${idFragment(options.parser)}_parser_error_${index + 1}`,
|
|
2731
|
+
severity: 'error',
|
|
2732
|
+
code: error.code ?? error.reasonCode,
|
|
2733
|
+
phase: 'parse',
|
|
2734
|
+
kind: 'unsupportedSyntax',
|
|
2735
|
+
message: String(error.message ?? 'Parser reported a syntax error.'),
|
|
2736
|
+
path: input.sourcePath,
|
|
2737
|
+
span: spanFromLoc(error.loc ? { start: error.loc, end: error.loc } : undefined, input),
|
|
2738
|
+
metadata: {
|
|
2739
|
+
parser: options.parser,
|
|
2740
|
+
reasonCode: error.reasonCode
|
|
2741
|
+
}
|
|
2742
|
+
}));
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
function nativeNodeId(context, kind, loc, propertyPath) {
|
|
2746
|
+
context.counter += 1;
|
|
2747
|
+
const start = loc?.start;
|
|
2748
|
+
const line = start?.line ?? 'x';
|
|
2749
|
+
const column = start?.column ?? 'x';
|
|
2750
|
+
return `native_${idFragment(kind)}_${idFragment(line)}_${idFragment(column)}_${context.counter}_${idFragment(propertyPath)}`;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
function isSyntaxAstNode(value) {
|
|
2754
|
+
return Boolean(value && typeof value === 'object' && typeof (value.type ?? value.kind) === 'string');
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
function ignoredSyntaxField(key) {
|
|
2758
|
+
return key === 'type'
|
|
2759
|
+
|| key === 'kind'
|
|
2760
|
+
|| key === 'loc'
|
|
2761
|
+
|| key === 'start'
|
|
2762
|
+
|| key === 'end'
|
|
2763
|
+
|| key === 'range'
|
|
2764
|
+
|| key === 'comments'
|
|
2765
|
+
|| key === 'leadingComments'
|
|
2766
|
+
|| key === 'trailingComments'
|
|
2767
|
+
|| key === 'innerComments'
|
|
2768
|
+
|| key === 'tokens'
|
|
2769
|
+
|| key === 'extra'
|
|
2770
|
+
|| key === 'parent';
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
function primitiveSyntaxFields(node) {
|
|
2774
|
+
const fields = {};
|
|
2775
|
+
for (const key of ['name', 'operator', 'sourceType', 'async', 'generator', 'computed', 'static', 'exportKind', 'importKind', 'optional']) {
|
|
2776
|
+
if (typeof node[key] === 'string' || typeof node[key] === 'number' || typeof node[key] === 'boolean' || node[key] === null) {
|
|
2777
|
+
fields[key] = node[key];
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
const literal = literalSyntaxValue(node);
|
|
2781
|
+
if (literal !== undefined) fields.literal = literal;
|
|
2782
|
+
if (node.source && typeof node.source === 'object' && typeof node.source.value === 'string') fields.source = node.source.value;
|
|
2783
|
+
return fields;
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
function literalSyntaxValue(node) {
|
|
2787
|
+
if (node.value === null || typeof node.value === 'string' || typeof node.value === 'number' || typeof node.value === 'boolean') return node.value;
|
|
2788
|
+
return undefined;
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
function spanFromLoc(loc, input) {
|
|
2792
|
+
if (!loc?.start) return undefined;
|
|
2793
|
+
return {
|
|
2794
|
+
sourceId: input.sourceHash,
|
|
2795
|
+
path: input.sourcePath ?? loc.filename,
|
|
2796
|
+
startLine: loc.start.line,
|
|
2797
|
+
startColumn: typeof loc.start.column === 'number' ? loc.start.column + 1 : undefined,
|
|
2798
|
+
endLine: loc.end?.line,
|
|
2799
|
+
endColumn: typeof loc.end?.column === 'number' ? loc.end.column + 1 : undefined
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
function syntaxDeclaration(node, nativeNodeId, input) {
|
|
2804
|
+
const kind = String(node.type ?? node.kind ?? '');
|
|
2805
|
+
if (kind === 'ImportDeclaration') {
|
|
2806
|
+
const name = node.source?.value;
|
|
2807
|
+
if (typeof name === 'string') return declarationRecord(input, nativeNodeId, name, 'module', 'import');
|
|
2808
|
+
}
|
|
2809
|
+
if (kind === 'ExportNamedDeclaration' || kind === 'ExportAllDeclaration') {
|
|
2810
|
+
const name = node.source?.value;
|
|
2811
|
+
if (typeof name === 'string') return declarationRecord(input, nativeNodeId, name, 'module', 'export');
|
|
2812
|
+
}
|
|
2813
|
+
if (kind === 'FunctionDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'function');
|
|
2814
|
+
if (kind === 'ClassDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'class');
|
|
2815
|
+
if (kind === 'TSInterfaceDeclaration' || kind === 'InterfaceDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'interface');
|
|
2816
|
+
if (kind === 'TSTypeAliasDeclaration' || kind === 'TypeAliasDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'type');
|
|
2817
|
+
if (kind === 'VariableDeclarator') return namedDeclaration(input, nativeNodeId, node.id, 'variable');
|
|
2818
|
+
return undefined;
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
function typeScriptDeclaration(node, kind, nativeNodeId, input) {
|
|
2822
|
+
if (kind === 'ImportDeclaration' || kind === 'ImportEqualsDeclaration') {
|
|
2823
|
+
const name = stringFromTsExpression(node.moduleSpecifier) ?? stringFromTsExpression(node.externalModuleReference?.expression);
|
|
2824
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
|
|
2825
|
+
}
|
|
2826
|
+
if (kind === 'FunctionDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'function');
|
|
2827
|
+
if (kind === 'ClassDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'class');
|
|
2828
|
+
if (kind === 'InterfaceDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'interface');
|
|
2829
|
+
if (kind === 'TypeAliasDeclaration' || kind === 'EnumDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'type');
|
|
2830
|
+
if (kind === 'VariableDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'variable');
|
|
2831
|
+
if (kind === 'MethodDeclaration' || kind === 'MethodSignature') return namedDeclaration(input, nativeNodeId, node.name, 'method');
|
|
2832
|
+
return undefined;
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
function treeSitterDeclaration(node, kind, nativeNodeId, input) {
|
|
2836
|
+
if (/import|include|use/.test(kind)) {
|
|
2837
|
+
const name = treeSitterFieldText(node, 'path') ?? treeSitterFieldText(node, 'source') ?? shortNodeText(node);
|
|
2838
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
|
|
2839
|
+
}
|
|
2840
|
+
if (/function|method|fn_item|function_declaration/.test(kind)) {
|
|
2841
|
+
const name = treeSitterFieldText(node, 'name');
|
|
2842
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'function', 'definition');
|
|
2843
|
+
}
|
|
2844
|
+
if (/class/.test(kind)) {
|
|
2845
|
+
const name = treeSitterFieldText(node, 'name');
|
|
2846
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'class', 'definition');
|
|
2847
|
+
}
|
|
2848
|
+
if (/interface/.test(kind)) {
|
|
2849
|
+
const name = treeSitterFieldText(node, 'name');
|
|
2850
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'interface', 'definition');
|
|
2851
|
+
}
|
|
2852
|
+
if (/struct|enum|type/.test(kind)) {
|
|
2853
|
+
const name = treeSitterFieldText(node, 'name');
|
|
2854
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'type', 'definition');
|
|
2855
|
+
}
|
|
2856
|
+
return undefined;
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
function namedDeclaration(input, nativeNodeId, nameNode, symbolKind) {
|
|
2860
|
+
const name = identifierName(nameNode);
|
|
2861
|
+
return name ? declarationRecord(input, nativeNodeId, name, symbolKind, 'definition') : undefined;
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
function declarationRecord(input, nativeNodeId, name, symbolKind, role = 'definition') {
|
|
2865
|
+
return {
|
|
2866
|
+
name: String(name),
|
|
2867
|
+
symbolKind,
|
|
2868
|
+
role,
|
|
2869
|
+
symbolId: `symbol:${input.language}:${role === 'import' ? 'import:' : ''}${idFragment(name)}`,
|
|
2870
|
+
nativeNodeId
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
function relationPredicateForDeclaration(declaration) {
|
|
2875
|
+
if (declaration.role === 'import') return 'imports';
|
|
2876
|
+
if (declaration.role === 'export') return 'exports';
|
|
2877
|
+
return 'defines';
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
function identifierName(node) {
|
|
2881
|
+
if (!node) return undefined;
|
|
2882
|
+
if (typeof node === 'string') return node;
|
|
2883
|
+
if (typeof node.name === 'string') return node.name;
|
|
2884
|
+
if (typeof node.escapedText === 'string') return node.escapedText;
|
|
2885
|
+
if (typeof node.text === 'string') return node.text;
|
|
2886
|
+
if (node.type === 'Identifier' && typeof node.value === 'string') return node.value;
|
|
2887
|
+
return undefined;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
function stringFromTsExpression(node) {
|
|
2891
|
+
if (!node) return undefined;
|
|
2892
|
+
if (typeof node.text === 'string') return node.text;
|
|
2893
|
+
if (typeof node.value === 'string') return node.value;
|
|
2894
|
+
return identifierName(node);
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
function typeScriptKindName(node, ts) {
|
|
2898
|
+
if (typeof node.kindName === 'string') return node.kindName;
|
|
2899
|
+
if (ts?.SyntaxKind && node.kind !== undefined) return ts.SyntaxKind[node.kind] ?? `SyntaxKind${node.kind}`;
|
|
2900
|
+
if (typeof node.kind === 'string') return node.kind;
|
|
2901
|
+
return `SyntaxKind${node.kind ?? 'Unknown'}`;
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
function spanFromTypeScriptNode(node, sourceFile) {
|
|
2905
|
+
const start = typeof node.getStart === 'function' ? node.getStart(sourceFile) : node.pos;
|
|
2906
|
+
const end = typeof node.getEnd === 'function' ? node.getEnd() : node.end;
|
|
2907
|
+
if (typeof start !== 'number' || typeof sourceFile?.getLineAndCharacterOfPosition !== 'function') return undefined;
|
|
2908
|
+
const startPos = sourceFile.getLineAndCharacterOfPosition(start);
|
|
2909
|
+
const endPos = typeof end === 'number' ? sourceFile.getLineAndCharacterOfPosition(end) : undefined;
|
|
2910
|
+
return {
|
|
2911
|
+
sourceId: sourceFile.sourceHash,
|
|
2912
|
+
path: sourceFile.fileName,
|
|
2913
|
+
startLine: startPos.line + 1,
|
|
2914
|
+
startColumn: startPos.character + 1,
|
|
2915
|
+
endLine: endPos ? endPos.line + 1 : undefined,
|
|
2916
|
+
endColumn: endPos ? endPos.character + 1 : undefined
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
function typeScriptNodeValue(node) {
|
|
2921
|
+
return identifierName(node.name) ?? stringFromTsExpression(node.moduleSpecifier) ?? undefined;
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
function primitiveTypeScriptFields(node, kind) {
|
|
2925
|
+
const fields = { kind };
|
|
2926
|
+
const name = identifierName(node.name);
|
|
2927
|
+
if (name) fields.name = name;
|
|
2928
|
+
const moduleSpecifier = stringFromTsExpression(node.moduleSpecifier);
|
|
2929
|
+
if (moduleSpecifier) fields.moduleSpecifier = moduleSpecifier;
|
|
2930
|
+
return fields;
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
function spanFromTreeSitterNode(node, input) {
|
|
2934
|
+
const start = node.startPosition;
|
|
2935
|
+
if (!start) return undefined;
|
|
2936
|
+
const end = node.endPosition;
|
|
2937
|
+
return {
|
|
2938
|
+
sourceId: input.sourceHash,
|
|
2939
|
+
path: input.sourcePath,
|
|
2940
|
+
startLine: start.row + 1,
|
|
2941
|
+
startColumn: start.column + 1,
|
|
2942
|
+
endLine: end ? end.row + 1 : undefined,
|
|
2943
|
+
endColumn: end ? end.column + 1 : undefined
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
function treeSitterFieldText(node, field) {
|
|
2948
|
+
if (typeof node.childForFieldName !== 'function') return undefined;
|
|
2949
|
+
return shortNodeText(node.childForFieldName(field));
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
function shortNodeText(node) {
|
|
2953
|
+
if (!node || typeof node.text !== 'string') return undefined;
|
|
2954
|
+
const text = node.text.trim();
|
|
2955
|
+
if (!text || text.length > 160) return undefined;
|
|
2956
|
+
return text.replace(/^['"]|['"]$/g, '');
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
function truncatedAstLoss(input, context, options) {
|
|
2960
|
+
return {
|
|
2961
|
+
id: `loss_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}_truncated`,
|
|
2962
|
+
severity: 'warning',
|
|
2963
|
+
phase: 'read',
|
|
2964
|
+
sourceFormat: input.language,
|
|
2965
|
+
kind: 'opaqueNative',
|
|
2966
|
+
message: `Native AST normalization stopped after ${context.maxNodes} node(s).`,
|
|
2967
|
+
metadata: {
|
|
2968
|
+
parser: options.parser,
|
|
2969
|
+
astFormat: options.astFormat,
|
|
2970
|
+
maxNodes: context.maxNodes
|
|
2971
|
+
}
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
function uniqueStrings(values) {
|
|
2976
|
+
return [...new Set((values ?? []).map((value) => String(value)).filter(Boolean))];
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
function uniqueByLossId(values) {
|
|
2980
|
+
const seen = new Set();
|
|
2981
|
+
const result = [];
|
|
2982
|
+
for (const value of values ?? []) {
|
|
2983
|
+
const id = value?.id ?? `loss_${result.length + 1}`;
|
|
2984
|
+
if (seen.has(id)) continue;
|
|
2985
|
+
seen.add(id);
|
|
2986
|
+
result.push(value.id ? value : { ...value, id });
|
|
2987
|
+
}
|
|
2988
|
+
return result;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
function uniqueByEvidenceId(values) {
|
|
2992
|
+
const seen = new Set();
|
|
2993
|
+
const result = [];
|
|
2994
|
+
for (const value of values ?? []) {
|
|
2995
|
+
const id = value?.id ?? `evidence_${result.length + 1}`;
|
|
2996
|
+
if (seen.has(id)) continue;
|
|
2997
|
+
seen.add(id);
|
|
2998
|
+
result.push(value.id ? value : { ...value, id });
|
|
2999
|
+
}
|
|
3000
|
+
return result;
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
function numberOrUndefined(value) {
|
|
3004
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
3005
|
+
}
|
|
3006
|
+
|
|
862
3007
|
function idFragment(value) {
|
|
863
3008
|
return String(value ?? 'native')
|
|
864
3009
|
.toLowerCase()
|