@shapeshift-labs/frontier-lang-compiler 0.2.7 → 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 +172 -0
- package/dist/index.js +1856 -53
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
hashSemanticValue,
|
|
11
11
|
nativeSourceNode,
|
|
12
12
|
stableUniversalAstJson,
|
|
13
|
+
validateSourceMapRecord,
|
|
13
14
|
validateUniversalAstEnvelope
|
|
14
15
|
} from '@shapeshift-labs/frontier-lang-kernel';
|
|
15
16
|
import { parseFrontierFile, parseFrontierSource } from '@shapeshift-labs/frontier-lang-parser';
|
|
@@ -52,6 +53,56 @@ const canonicalTargets = Object.freeze({
|
|
|
52
53
|
h: 'c'
|
|
53
54
|
});
|
|
54
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
|
+
|
|
55
106
|
export function normalizeCompileTarget(target) {
|
|
56
107
|
const normalized = String(target ?? 'typescript').toLowerCase();
|
|
57
108
|
const canonical = canonicalTargets[normalized] ?? normalized;
|
|
@@ -136,6 +187,187 @@ export function resolveCapabilityAdapters(document, target = 'typescript', optio
|
|
|
136
187
|
});
|
|
137
188
|
}
|
|
138
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
|
+
|
|
139
371
|
export async function runNativeImporterAdapter(adapter, input = {}) {
|
|
140
372
|
const summary = normalizeNativeImporterAdapter(adapter);
|
|
141
373
|
const language = input.language ?? summary.language;
|
|
@@ -260,6 +492,8 @@ export function importNativeSource(input) {
|
|
|
260
492
|
if (!language) throw new Error('importNativeSource requires a language or nativeAst.language');
|
|
261
493
|
const sourcePath = input.sourcePath ?? input.nativeAst?.sourcePath;
|
|
262
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;
|
|
263
497
|
const importIdPart = idFragment(input.id ?? input.nativeSourceId ?? sourcePath ?? language);
|
|
264
498
|
const lightweight = !input.nativeAst && !input.nodes && input.sourceText
|
|
265
499
|
? createLightweightNativeImport({
|
|
@@ -295,7 +529,9 @@ export function importNativeSource(input) {
|
|
|
295
529
|
}
|
|
296
530
|
});
|
|
297
531
|
const frontierNodeIds = input.frontierNodeIds ?? input.semanticNodes?.map((node) => node.id) ?? [];
|
|
298
|
-
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 ?? []);
|
|
299
535
|
const nativeSource = nativeSourceNode({
|
|
300
536
|
id: input.nativeSourceId ?? `native_source_${importIdPart}`,
|
|
301
537
|
name: input.name ?? sourcePath?.split(/[\\/]/).filter(Boolean).at(-1) ?? `${language}NativeSource`,
|
|
@@ -310,12 +546,11 @@ export function importNativeSource(input) {
|
|
|
310
546
|
losses,
|
|
311
547
|
target: input.target,
|
|
312
548
|
metadata: {
|
|
313
|
-
semanticStatus
|
|
549
|
+
semanticStatus,
|
|
314
550
|
mappings: input.mappings ?? [],
|
|
315
551
|
...input.nativeSourceMetadata
|
|
316
552
|
}
|
|
317
553
|
});
|
|
318
|
-
const semanticNodes = input.semanticNodes ?? [];
|
|
319
554
|
const document = createDocument({
|
|
320
555
|
id: input.documentId ?? `document_${importIdPart}`,
|
|
321
556
|
name: input.documentName ?? nativeSource.name,
|
|
@@ -323,11 +558,11 @@ export function importNativeSource(input) {
|
|
|
323
558
|
rootIds: input.rootIds,
|
|
324
559
|
metadata: {
|
|
325
560
|
sourceLanguage: language,
|
|
326
|
-
semanticStatus
|
|
561
|
+
semanticStatus,
|
|
327
562
|
...input.documentMetadata
|
|
328
563
|
}
|
|
329
564
|
});
|
|
330
|
-
const
|
|
565
|
+
const baseEvidence = input.evidence ?? [{
|
|
331
566
|
id: input.evidenceId ?? `evidence_${importIdPart}_import`,
|
|
332
567
|
kind: 'import',
|
|
333
568
|
status: losses.some((loss) => loss.severity === 'error') ? 'failed' : 'passed',
|
|
@@ -336,9 +571,17 @@ export function importNativeSource(input) {
|
|
|
336
571
|
metadata: {
|
|
337
572
|
parser: nativeAst.parser,
|
|
338
573
|
sourcePath,
|
|
339
|
-
semanticStatus
|
|
574
|
+
semanticStatus
|
|
340
575
|
}
|
|
341
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);
|
|
342
585
|
const semanticIndex = input.semanticIndex ?? lightweight?.semanticIndex;
|
|
343
586
|
const sourceMapMappings = normalizeSourceMapMappings(
|
|
344
587
|
input.mappings ?? lightweight?.mappings ?? inferSourceMapMappings({
|
|
@@ -353,16 +596,20 @@ export function importNativeSource(input) {
|
|
|
353
596
|
nativeSource,
|
|
354
597
|
evidence,
|
|
355
598
|
losses,
|
|
356
|
-
target: input.target
|
|
599
|
+
target: input.target,
|
|
600
|
+
targetPath,
|
|
601
|
+
targetHash
|
|
357
602
|
}
|
|
358
603
|
);
|
|
604
|
+
const inferredTargetPath = targetPath ?? commonGeneratedTargetPath(sourceMapMappings);
|
|
359
605
|
const inferredSourceMaps = sourceMapMappings.length
|
|
360
606
|
? [createSourceMapRecord({
|
|
361
607
|
id: input.sourceMapId ?? `source_map_${importIdPart}`,
|
|
362
608
|
sourcePath,
|
|
363
609
|
sourceHash,
|
|
364
610
|
target: input.target,
|
|
365
|
-
targetPath:
|
|
611
|
+
targetPath: inferredTargetPath,
|
|
612
|
+
targetHash,
|
|
366
613
|
semanticIndexId: semanticIndex?.id,
|
|
367
614
|
nativeAstId: nativeAst.id,
|
|
368
615
|
nativeSourceId: nativeSource.id,
|
|
@@ -371,11 +618,26 @@ export function importNativeSource(input) {
|
|
|
371
618
|
metadata: {
|
|
372
619
|
sourceLanguage: language,
|
|
373
620
|
parser: nativeAst.parser,
|
|
374
|
-
semanticStatus
|
|
621
|
+
semanticStatus
|
|
375
622
|
}
|
|
376
623
|
})]
|
|
377
624
|
: [];
|
|
378
|
-
const sourceMaps = input.sourceMaps ?? inferredSourceMaps
|
|
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 ?? []);
|
|
379
641
|
const universalAst = createUniversalAstEnvelope({
|
|
380
642
|
id: input.universalAstId ?? `universal_ast_${importIdPart}`,
|
|
381
643
|
document,
|
|
@@ -387,7 +649,8 @@ export function importNativeSource(input) {
|
|
|
387
649
|
metadata: {
|
|
388
650
|
sourceLanguage: language,
|
|
389
651
|
sourcePath,
|
|
390
|
-
semanticStatus
|
|
652
|
+
semanticStatus,
|
|
653
|
+
nativeImportLossSummary: lossSummary,
|
|
391
654
|
...input.universalAstMetadata
|
|
392
655
|
}
|
|
393
656
|
});
|
|
@@ -406,32 +669,35 @@ export function importNativeSource(input) {
|
|
|
406
669
|
sourcePath,
|
|
407
670
|
semanticIndexId: semanticIndex?.id,
|
|
408
671
|
universalAstId: universalAst.id,
|
|
409
|
-
sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.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
|
|
410
697
|
}
|
|
411
698
|
});
|
|
412
699
|
return {
|
|
413
|
-
...
|
|
414
|
-
id: input.id ?? `import_${importIdPart}`,
|
|
415
|
-
language,
|
|
416
|
-
sourcePath,
|
|
417
|
-
document,
|
|
418
|
-
patch,
|
|
419
|
-
nativeAst,
|
|
420
|
-
semanticIndex,
|
|
421
|
-
universalAst,
|
|
422
|
-
sourceMaps,
|
|
423
|
-
losses,
|
|
424
|
-
evidence,
|
|
425
|
-
metadata: {
|
|
426
|
-
nativeSourceId: nativeSource.id,
|
|
427
|
-
semanticIndexId: semanticIndex?.id,
|
|
428
|
-
universalAstId: universalAst.id,
|
|
429
|
-
sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
|
|
430
|
-
semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only'),
|
|
431
|
-
mappings: sourceMapMappings,
|
|
432
|
-
...input.metadata
|
|
433
|
-
}
|
|
434
|
-
}),
|
|
700
|
+
...withNativeImportReadiness(importResult, lossSummary),
|
|
435
701
|
nativeSource
|
|
436
702
|
};
|
|
437
703
|
}
|
|
@@ -566,6 +832,17 @@ function scanNativeDeclarations(input) {
|
|
|
566
832
|
if (language === 'csharp' || language === 'c#') return scanCSharp(input);
|
|
567
833
|
if (language === 'php') return scanPhp(input);
|
|
568
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);
|
|
569
846
|
return scanGenericDeclarations(input);
|
|
570
847
|
}
|
|
571
848
|
|
|
@@ -768,6 +1045,298 @@ function scanRuby(input) {
|
|
|
768
1045
|
return declarations;
|
|
769
1046
|
}
|
|
770
1047
|
|
|
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;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
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;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
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;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
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
|
+
|
|
771
1340
|
function scanGenericDeclarations(input) {
|
|
772
1341
|
return sourceLines(input.sourceText)
|
|
773
1342
|
.filter(({ line }) => /\b(function|class|struct|enum|trait|interface|def)\b/.test(line))
|
|
@@ -804,6 +1373,78 @@ function phpSymbolKind(kind) {
|
|
|
804
1373
|
return 'class';
|
|
805
1374
|
}
|
|
806
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
|
+
|
|
807
1448
|
function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fields = {}, hasBody = false) {
|
|
808
1449
|
const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
|
|
809
1450
|
return {
|
|
@@ -990,33 +1631,61 @@ function inferSourceMapMappings(input) {
|
|
|
990
1631
|
}
|
|
991
1632
|
|
|
992
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
|
+
}
|
|
993
1638
|
const semanticIndex = context.semanticIndex;
|
|
994
1639
|
const nativeAst = context.nativeAst;
|
|
995
1640
|
const nativeSource = context.nativeSource;
|
|
996
1641
|
const symbolsById = new Map((semanticIndex?.symbols ?? []).map((symbol) => [symbol.id, symbol]));
|
|
997
1642
|
const occurrencesById = new Map((semanticIndex?.occurrences ?? []).map((occurrence) => [occurrence.id, occurrence]));
|
|
998
|
-
const evidenceIds = (
|
|
999
|
-
|
|
1000
|
-
.
|
|
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
|
|
1001
1650
|
.map((mapping, index) => {
|
|
1651
|
+
if (!mapping || typeof mapping !== 'object') {
|
|
1652
|
+
throw new Error(`Source-map mapping ${index + 1} must be an object`);
|
|
1653
|
+
}
|
|
1002
1654
|
const occurrence = mapping.semanticOccurrenceId ? occurrencesById.get(mapping.semanticOccurrenceId) : undefined;
|
|
1003
1655
|
const symbol = mapping.semanticSymbolId ? symbolsById.get(mapping.semanticSymbolId) : occurrence ? symbolsById.get(occurrence.symbolId) : undefined;
|
|
1004
1656
|
const nativeAstNodeId = mapping.nativeAstNodeId ?? occurrence?.nativeAstNodeId;
|
|
1005
1657
|
const nativeNode = nativeAstNodeId ? nativeAst?.nodes?.[nativeAstNodeId] : undefined;
|
|
1006
1658
|
const sourceSpan = mapping.sourceSpan ?? occurrence?.span ?? nativeNode?.span;
|
|
1007
|
-
|
|
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 = {
|
|
1008
1673
|
...mapping,
|
|
1009
|
-
id: mapping.id ?? `map_${idFragment(nativeAstNodeId ?? mapping.semanticSymbolId ?? mapping.semanticNodeId ?? index + 1)}`,
|
|
1010
1674
|
nativeSourceId: mapping.nativeSourceId ?? nativeSource?.id,
|
|
1011
1675
|
nativeAstNodeId,
|
|
1012
1676
|
semanticSymbolId: mapping.semanticSymbolId ?? occurrence?.symbolId,
|
|
1013
1677
|
semanticOccurrenceId: mapping.semanticOccurrenceId ?? occurrence?.id,
|
|
1014
1678
|
semanticNodeId: mapping.semanticNodeId ?? occurrence?.semanticNodeId ?? symbol?.semanticNodeId,
|
|
1015
1679
|
sourceSpan,
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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)
|
|
1020
1689
|
};
|
|
1021
1690
|
});
|
|
1022
1691
|
}
|
|
@@ -1028,19 +1697,293 @@ function lossIdsForNativeNode(losses, nativeAstNodeId) {
|
|
|
1028
1697
|
.map((loss) => loss.id);
|
|
1029
1698
|
}
|
|
1030
1699
|
|
|
1031
|
-
|
|
1032
|
-
return
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
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;
|
|
1039
1748
|
});
|
|
1040
1749
|
}
|
|
1041
1750
|
|
|
1042
|
-
|
|
1043
|
-
|
|
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) {
|
|
1986
|
+
const envelope = JSON.parse(source);
|
|
1044
1987
|
const issues = validateUniversalAstEnvelope(envelope);
|
|
1045
1988
|
if (issues.length > 0) {
|
|
1046
1989
|
throw new Error(`Invalid Frontier universal AST JSON: ${issues.join('; ')}`);
|
|
@@ -1060,6 +2003,536 @@ export function emitForTarget(document, target = 'typescript', options = {}) {
|
|
|
1060
2003
|
return renderTargetAst(projectFrontierAst(document, target, options), target);
|
|
1061
2004
|
}
|
|
1062
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
|
+
|
|
1063
2536
|
function normalizeNativeImporterAdapter(adapter) {
|
|
1064
2537
|
if (!adapter || typeof adapter !== 'object') {
|
|
1065
2538
|
throw new Error('Native importer adapter must be an object');
|
|
@@ -1201,6 +2674,336 @@ function serializableDiagnostic(diagnostic) {
|
|
|
1201
2674
|
};
|
|
1202
2675
|
}
|
|
1203
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
|
+
|
|
1204
3007
|
function idFragment(value) {
|
|
1205
3008
|
return String(value ?? 'native')
|
|
1206
3009
|
.toLowerCase()
|