@shapeshift-labs/frontier-lang-compiler 0.2.7 → 0.2.9
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 +117 -0
- package/bench/smoke.mjs +73 -2
- package/dist/index.d.ts +414 -0
- package/dist/index.js +2963 -143
- 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,112 @@ 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
|
+
|
|
106
|
+
export const NativeImportLanguageProfiles = Object.freeze([
|
|
107
|
+
nativeImportLanguageProfile('javascript', {
|
|
108
|
+
aliases: ['js', 'mjs', 'cjs', 'jsx'],
|
|
109
|
+
extensions: ['.js', '.mjs', '.cjs', '.jsx'],
|
|
110
|
+
parserAdapters: ['estree', 'babel', 'tree-sitter'],
|
|
111
|
+
lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'dynamicRuntime']
|
|
112
|
+
}),
|
|
113
|
+
nativeImportLanguageProfile('typescript', {
|
|
114
|
+
aliases: ['ts', 'tsx'],
|
|
115
|
+
extensions: ['.ts', '.tsx'],
|
|
116
|
+
parserAdapters: ['typescript-compiler-api', 'babel', 'tree-sitter'],
|
|
117
|
+
lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'unsupportedSyntax']
|
|
118
|
+
}),
|
|
119
|
+
nativeImportLanguageProfile('python', {
|
|
120
|
+
aliases: ['py'],
|
|
121
|
+
extensions: ['.py', '.pyi'],
|
|
122
|
+
parserAdapters: ['python-ast', 'libcst', 'parso', 'tree-sitter'],
|
|
123
|
+
lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'dynamicRuntime']
|
|
124
|
+
}),
|
|
125
|
+
nativeImportLanguageProfile('rust', {
|
|
126
|
+
aliases: ['rs'],
|
|
127
|
+
extensions: ['.rs'],
|
|
128
|
+
parserAdapters: ['syn', 'rust-analyzer-rowan', 'tree-sitter'],
|
|
129
|
+
lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation']
|
|
130
|
+
}),
|
|
131
|
+
nativeImportLanguageProfile('c', {
|
|
132
|
+
aliases: ['h'],
|
|
133
|
+
extensions: ['.c', '.h'],
|
|
134
|
+
parserAdapters: ['clang', 'libclang', 'tree-sitter'],
|
|
135
|
+
lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'preprocessor', 'sourceMapApproximation', 'sourcePreservation']
|
|
136
|
+
}),
|
|
137
|
+
nativeImportLanguageProfile('cpp', {
|
|
138
|
+
aliases: ['c++', 'cc', 'cxx', 'hpp'],
|
|
139
|
+
extensions: ['.cc', '.cpp', '.cxx', '.hpp', '.hh'],
|
|
140
|
+
parserAdapters: ['clang', 'libclang', 'tree-sitter'],
|
|
141
|
+
lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'preprocessor', 'metaprogramming', 'sourceMapApproximation', 'sourcePreservation']
|
|
142
|
+
}),
|
|
143
|
+
nativeImportLanguageProfile('java', { extensions: ['.java'], parserAdapters: ['javac', 'jdt', 'javaparser', 'tree-sitter'] }),
|
|
144
|
+
nativeImportLanguageProfile('go', { extensions: ['.go'], parserAdapters: ['go/parser', 'tree-sitter'] }),
|
|
145
|
+
nativeImportLanguageProfile('swift', { extensions: ['.swift'], parserAdapters: ['swift-syntax', 'tree-sitter'] }),
|
|
146
|
+
nativeImportLanguageProfile('csharp', { aliases: ['c#', 'cs'], extensions: ['.cs'], parserAdapters: ['roslyn', 'tree-sitter'] }),
|
|
147
|
+
nativeImportLanguageProfile('php', { extensions: ['.php'], parserAdapters: ['php-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'metaprogramming', 'sourceMapApproximation', 'sourcePreservation'] }),
|
|
148
|
+
nativeImportLanguageProfile('ruby', { aliases: ['rb'], extensions: ['.rb', '.rake'], parserAdapters: ['prism', 'ripper', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'metaprogramming', 'sourceMapApproximation', 'sourcePreservation'] }),
|
|
149
|
+
nativeImportLanguageProfile('kotlin', { aliases: ['kt', 'kts'], extensions: ['.kt', '.kts'], parserAdapters: ['kotlin-compiler', 'intellij-psi', 'tree-sitter'] }),
|
|
150
|
+
nativeImportLanguageProfile('scala', { aliases: ['sc'], extensions: ['.scala', '.sc'], parserAdapters: ['scala-compiler', 'scalameta', 'tree-sitter'] }),
|
|
151
|
+
nativeImportLanguageProfile('dart', { extensions: ['.dart'], parserAdapters: ['dart-analyzer', 'tree-sitter'] }),
|
|
152
|
+
nativeImportLanguageProfile('lua', { extensions: ['.lua'], parserAdapters: ['luaparse', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] }),
|
|
153
|
+
nativeImportLanguageProfile('shell', { aliases: ['sh', 'bash', 'zsh'], extensions: ['.sh', '.bash', '.zsh'], parserAdapters: ['bash-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] }),
|
|
154
|
+
nativeImportLanguageProfile('sql', { aliases: ['postgresql', 'postgres', 'mysql', 'sqlite'], extensions: ['.sql'], parserAdapters: ['sqlparser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'unsupportedSyntax', 'sourceMapApproximation', 'sourcePreservation'] }),
|
|
155
|
+
nativeImportLanguageProfile('zig', { extensions: ['.zig'], parserAdapters: ['zig-ast', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'generatedCode', 'sourceMapApproximation', 'sourcePreservation'] }),
|
|
156
|
+
nativeImportLanguageProfile('elixir', { aliases: ['ex', 'exs'], extensions: ['.ex', '.exs'], parserAdapters: ['elixir-quoted', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation'] }),
|
|
157
|
+
nativeImportLanguageProfile('erlang', { aliases: ['erl', 'hrl'], extensions: ['.erl', '.hrl'], parserAdapters: ['erl_parse', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'preprocessor', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation'] }),
|
|
158
|
+
nativeImportLanguageProfile('haskell', { aliases: ['hs'], extensions: ['.hs', '.lhs'], parserAdapters: ['ghc-api', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation'] }),
|
|
159
|
+
nativeImportLanguageProfile('r', { aliases: ['R'], extensions: ['.r', '.R'], parserAdapters: ['r-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] })
|
|
160
|
+
]);
|
|
161
|
+
|
|
55
162
|
export function normalizeCompileTarget(target) {
|
|
56
163
|
const normalized = String(target ?? 'typescript').toLowerCase();
|
|
57
164
|
const canonical = canonicalTargets[normalized] ?? normalized;
|
|
@@ -136,6 +243,298 @@ export function resolveCapabilityAdapters(document, target = 'typescript', optio
|
|
|
136
243
|
});
|
|
137
244
|
}
|
|
138
245
|
|
|
246
|
+
export function summarizeNativeImportLosses(losses = [], options = {}) {
|
|
247
|
+
const normalizedLosses = normalizeNativeLossRecords(losses);
|
|
248
|
+
const bySeverity = { info: 0, warning: 0, error: 0 };
|
|
249
|
+
const byKind = {};
|
|
250
|
+
const blockingLossIds = [];
|
|
251
|
+
const reviewLossIds = [];
|
|
252
|
+
const informationalLossIds = [];
|
|
253
|
+
let highestSeverity = 'none';
|
|
254
|
+
|
|
255
|
+
for (const loss of normalizedLosses) {
|
|
256
|
+
bySeverity[loss.severity] += 1;
|
|
257
|
+
byKind[loss.kind] = (byKind[loss.kind] ?? 0) + 1;
|
|
258
|
+
if (lossSeverityRank[loss.severity] > lossSeverityRank[highestSeverity]) {
|
|
259
|
+
highestSeverity = loss.severity;
|
|
260
|
+
}
|
|
261
|
+
if (loss.severity === 'error') blockingLossIds.push(loss.id);
|
|
262
|
+
else if (loss.severity === 'warning') reviewLossIds.push(loss.id);
|
|
263
|
+
else informationalLossIds.push(loss.id);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const failedEvidenceIds = (options.evidence ?? [])
|
|
267
|
+
.filter((record) => record?.status === 'failed')
|
|
268
|
+
.map((record) => record.id)
|
|
269
|
+
.filter(Boolean);
|
|
270
|
+
const exactAst = Boolean(options.exactAst) && normalizedLosses.length === 0;
|
|
271
|
+
const categories = uniqueStrings([
|
|
272
|
+
...(exactAst ? ['exactAstImport'] : []),
|
|
273
|
+
...normalizedLosses.map((loss) => nativeImportCategoryForLossKind(loss.kind))
|
|
274
|
+
]);
|
|
275
|
+
const semanticMergeReadiness = failedEvidenceIds.length
|
|
276
|
+
? 'blocked'
|
|
277
|
+
: NativeImportReadinessBySeverity[highestSeverity];
|
|
278
|
+
const readinessReasons = nativeImportReadinessReasons({
|
|
279
|
+
exactAst,
|
|
280
|
+
failedEvidenceIds,
|
|
281
|
+
blockingLossIds,
|
|
282
|
+
reviewLossIds,
|
|
283
|
+
informationalLossIds
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
total: normalizedLosses.length,
|
|
288
|
+
hasLosses: normalizedLosses.length > 0,
|
|
289
|
+
exactAst,
|
|
290
|
+
highestSeverity,
|
|
291
|
+
semanticMergeReadiness,
|
|
292
|
+
readinessReasons,
|
|
293
|
+
categories,
|
|
294
|
+
bySeverity,
|
|
295
|
+
byKind,
|
|
296
|
+
blockingLossIds,
|
|
297
|
+
reviewLossIds,
|
|
298
|
+
informationalLossIds,
|
|
299
|
+
failedEvidenceIds,
|
|
300
|
+
parser: options.parser,
|
|
301
|
+
scanKind: options.scanKind,
|
|
302
|
+
semanticStatus: options.semanticStatus
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function classifyNativeImportReadiness(losses = [], options = {}) {
|
|
307
|
+
const summary = summarizeNativeImportLosses(losses, options);
|
|
308
|
+
return {
|
|
309
|
+
readiness: summary.semanticMergeReadiness,
|
|
310
|
+
reasons: summary.readinessReasons,
|
|
311
|
+
summary
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function createNativeImportCoverageMatrix(input = {}) {
|
|
316
|
+
const imports = input.imports ?? [];
|
|
317
|
+
const adapters = input.adapters ?? [];
|
|
318
|
+
const profiles = mergeNativeImportProfiles(input.languages ?? NativeImportLanguageProfiles, imports, adapters);
|
|
319
|
+
const languages = profiles.map((profile) => nativeImportCoverageForProfile(profile, imports, adapters));
|
|
320
|
+
const summary = languages.reduce((totals, entry) => {
|
|
321
|
+
totals.languages += 1;
|
|
322
|
+
if (entry.supportsLightweightScan) totals.lightweightScanners += 1;
|
|
323
|
+
if (entry.parserAdapters.length) totals.parserAdapterSlots += entry.parserAdapters.length;
|
|
324
|
+
totals.imports += entry.imports.total;
|
|
325
|
+
totals.symbols += entry.imports.symbols;
|
|
326
|
+
totals.sourceMaps += entry.imports.sourceMaps;
|
|
327
|
+
totals.sourceMapMappings += entry.imports.sourceMapMappings;
|
|
328
|
+
totals.losses += entry.imports.losses;
|
|
329
|
+
totals.byReadiness[entry.imports.readiness] = (totals.byReadiness[entry.imports.readiness] ?? 0) + 1;
|
|
330
|
+
for (const [kind, count] of Object.entries(entry.imports.lossKinds)) {
|
|
331
|
+
totals.lossKinds[kind] = (totals.lossKinds[kind] ?? 0) + count;
|
|
332
|
+
}
|
|
333
|
+
return totals;
|
|
334
|
+
}, {
|
|
335
|
+
languages: 0,
|
|
336
|
+
lightweightScanners: 0,
|
|
337
|
+
parserAdapterSlots: 0,
|
|
338
|
+
imports: 0,
|
|
339
|
+
symbols: 0,
|
|
340
|
+
sourceMaps: 0,
|
|
341
|
+
sourceMapMappings: 0,
|
|
342
|
+
losses: 0,
|
|
343
|
+
byReadiness: {},
|
|
344
|
+
lossKinds: {}
|
|
345
|
+
});
|
|
346
|
+
return {
|
|
347
|
+
kind: 'frontier.lang.nativeImportCoverageMatrix',
|
|
348
|
+
version: 1,
|
|
349
|
+
generatedAt: input.generatedAt ?? Date.now(),
|
|
350
|
+
languages,
|
|
351
|
+
summary,
|
|
352
|
+
metadata: {
|
|
353
|
+
compileTargets: [...FrontierCompileTargets],
|
|
354
|
+
note: 'Coverage is evidence and capability metadata, not a claim that every language feature is losslessly portable.'
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function createSemanticImportSidecar(importResult, options = {}) {
|
|
360
|
+
const imports = Array.isArray(importResult?.imports) ? importResult.imports : [importResult].filter(Boolean);
|
|
361
|
+
const importEntries = imports.map((imported, index) => semanticImportSidecarEntry(imported, index, options));
|
|
362
|
+
const symbols = importEntries.flatMap((entry) => entry.symbols);
|
|
363
|
+
const ownershipRegions = uniqueRecordsById(importEntries.flatMap((entry) => entry.ownershipRegions));
|
|
364
|
+
const sourceMaps = imports.flatMap((imported) => imported?.sourceMaps ?? imported?.universalAst?.sourceMaps ?? []);
|
|
365
|
+
const sourceMapMappings = sourceMaps.flatMap((sourceMap) => sourceMap?.mappings ?? []);
|
|
366
|
+
const losses = imports.flatMap((imported) => imported?.losses ?? []);
|
|
367
|
+
const evidence = uniqueRecordsById(imports.flatMap((imported) => imported?.evidence ?? []));
|
|
368
|
+
const mergeCandidates = imports.flatMap((imported) => imported?.mergeCandidates ?? []);
|
|
369
|
+
const lossSummary = summarizeNativeImportLosses(losses, { evidence });
|
|
370
|
+
const readiness = mergeCandidates.reduce(
|
|
371
|
+
(current, candidate) => maxSemanticMergeReadiness(current, candidate.readiness),
|
|
372
|
+
lossSummary.semanticMergeReadiness
|
|
373
|
+
);
|
|
374
|
+
const patchHints = ownershipRegions.map((region) => semanticPatchHintForRegion(region, readiness, options));
|
|
375
|
+
return {
|
|
376
|
+
kind: 'frontier.lang.semanticImportSidecar',
|
|
377
|
+
version: 1,
|
|
378
|
+
id: options.id ?? `semantic_import_${idFragment(importResult?.id ?? importResult?.projectRoot ?? imports[0]?.sourcePath ?? imports[0]?.language ?? 'source')}`,
|
|
379
|
+
generatedAt: options.generatedAt ?? Date.now(),
|
|
380
|
+
language: importResult?.language ?? (imports.length === 1 ? imports[0]?.language : 'mixed'),
|
|
381
|
+
projectRoot: importResult?.projectRoot,
|
|
382
|
+
imports: importEntries.map(({ ownershipRegions: _regions, symbols: _symbols, ...entry }) => entry),
|
|
383
|
+
symbols,
|
|
384
|
+
ownershipRegions,
|
|
385
|
+
sourceMaps: {
|
|
386
|
+
total: sourceMaps.length,
|
|
387
|
+
mappings: sourceMapMappings.length,
|
|
388
|
+
ids: sourceMaps.map((sourceMap) => sourceMap.id).filter(Boolean)
|
|
389
|
+
},
|
|
390
|
+
patchHints,
|
|
391
|
+
mergeCandidates: mergeCandidates.map((candidate) => ({
|
|
392
|
+
id: candidate.id,
|
|
393
|
+
readiness: candidate.readiness,
|
|
394
|
+
reasons: candidate.reasons ?? [],
|
|
395
|
+
risk: candidate.risk,
|
|
396
|
+
operationCount: candidate.operations?.length ?? candidate.patch?.operations?.length ?? 0
|
|
397
|
+
})),
|
|
398
|
+
losses: {
|
|
399
|
+
total: losses.length,
|
|
400
|
+
byKind: lossSummary.byKind,
|
|
401
|
+
bySeverity: lossSummary.bySeverity,
|
|
402
|
+
categories: lossSummary.categories,
|
|
403
|
+
blockingLossIds: lossSummary.blockingLossIds,
|
|
404
|
+
reviewLossIds: lossSummary.reviewLossIds
|
|
405
|
+
},
|
|
406
|
+
evidence: {
|
|
407
|
+
total: evidence.length,
|
|
408
|
+
failed: evidence.filter((record) => record.status === 'failed').map((record) => record.id),
|
|
409
|
+
ids: evidence.map((record) => record.id)
|
|
410
|
+
},
|
|
411
|
+
summary: {
|
|
412
|
+
imports: imports.length,
|
|
413
|
+
symbols: symbols.length,
|
|
414
|
+
ownershipRegions: ownershipRegions.length,
|
|
415
|
+
sourceMapMappings: sourceMapMappings.length,
|
|
416
|
+
readiness,
|
|
417
|
+
emptySemanticIndex: symbols.length === 0
|
|
418
|
+
},
|
|
419
|
+
metadata: {
|
|
420
|
+
note: 'Sidecar is source-addressable semantic evidence for merge admission; lightweight scanner regions remain review-required unless exact parser evidence upgrades readiness.',
|
|
421
|
+
...options.metadata
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function createEstreeNativeImporterAdapter(options = {}) {
|
|
427
|
+
return createJavaScriptSyntaxImporterAdapter({
|
|
428
|
+
id: 'frontier.estree-native-importer',
|
|
429
|
+
language: 'javascript',
|
|
430
|
+
parser: 'estree',
|
|
431
|
+
supportedExtensions: ['.js', '.mjs', '.cjs', '.jsx'],
|
|
432
|
+
astFormat: 'estree',
|
|
433
|
+
...options
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function createBabelNativeImporterAdapter(options = {}) {
|
|
438
|
+
return createJavaScriptSyntaxImporterAdapter({
|
|
439
|
+
id: 'frontier.babel-native-importer',
|
|
440
|
+
language: 'javascript',
|
|
441
|
+
parser: 'babel',
|
|
442
|
+
supportedExtensions: ['.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx'],
|
|
443
|
+
astFormat: 'babel',
|
|
444
|
+
defaultParserOptions: {
|
|
445
|
+
errorRecovery: true,
|
|
446
|
+
ranges: true,
|
|
447
|
+
sourceType: 'unambiguous',
|
|
448
|
+
plugins: ['typescript', 'jsx']
|
|
449
|
+
},
|
|
450
|
+
...options
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function createTypeScriptCompilerNativeImporterAdapter(options = {}) {
|
|
455
|
+
return {
|
|
456
|
+
id: options.id ?? 'frontier.typescript-compiler-native-importer',
|
|
457
|
+
language: options.language ?? 'typescript',
|
|
458
|
+
parser: options.parser ?? 'typescript-compiler-api',
|
|
459
|
+
version: options.version,
|
|
460
|
+
capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
|
|
461
|
+
supportedExtensions: options.supportedExtensions ?? ['.ts', '.tsx', '.js', '.jsx'],
|
|
462
|
+
diagnostics: options.diagnostics,
|
|
463
|
+
parse(input) {
|
|
464
|
+
const ts = options.typescript ?? options.ts ?? input.options?.typescript ?? input.options?.ts;
|
|
465
|
+
const sourceFile = input.options?.sourceFile ?? input.options?.ast ?? options.sourceFile ?? createTypeScriptSourceFile(ts, input, options);
|
|
466
|
+
if (!sourceFile) {
|
|
467
|
+
return missingInjectedParserResult(input, {
|
|
468
|
+
parser: options.parser ?? 'typescript-compiler-api',
|
|
469
|
+
adapterId: options.id ?? 'frontier.typescript-compiler-native-importer',
|
|
470
|
+
message: 'createTypeScriptCompilerNativeImporterAdapter requires an injected TypeScript module, createSourceFile function, sourceFile, or adapterOptions.sourceFile.'
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
return createNativeImportFromTypeScriptAst(sourceFile, input, {
|
|
474
|
+
parser: options.parser ?? 'typescript-compiler-api',
|
|
475
|
+
astFormat: 'typescript-compiler-api',
|
|
476
|
+
ts,
|
|
477
|
+
maxNodes: options.maxNodes,
|
|
478
|
+
includeTokens: options.includeTokens
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function createTreeSitterNativeImporterAdapter(options = {}) {
|
|
485
|
+
return {
|
|
486
|
+
id: options.id ?? `frontier.tree-sitter-${idFragment(options.language ?? 'source')}-native-importer`,
|
|
487
|
+
language: options.language ?? 'source',
|
|
488
|
+
parser: options.parserName ?? options.parser ?? 'tree-sitter',
|
|
489
|
+
version: options.version,
|
|
490
|
+
capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
|
|
491
|
+
supportedExtensions: options.supportedExtensions ?? [],
|
|
492
|
+
diagnostics: options.diagnostics,
|
|
493
|
+
parse(input) {
|
|
494
|
+
const tree = input.options?.tree ?? options.tree ?? parseTreeSitterSource(input, options);
|
|
495
|
+
const root = tree?.rootNode ?? tree;
|
|
496
|
+
if (!root) {
|
|
497
|
+
return missingInjectedParserResult(input, {
|
|
498
|
+
parser: options.parserName ?? options.parser ?? 'tree-sitter',
|
|
499
|
+
adapterId: options.id ?? `frontier.tree-sitter-${idFragment(options.language ?? input.language)}-native-importer`,
|
|
500
|
+
message: 'createTreeSitterNativeImporterAdapter requires an injected tree-sitter parser/tree or adapterOptions.tree.'
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
return createNativeImportFromTreeSitter(root, input, {
|
|
504
|
+
parser: options.parserName ?? options.parser ?? 'tree-sitter',
|
|
505
|
+
astFormat: 'tree-sitter',
|
|
506
|
+
maxNodes: options.maxNodes
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export async function importNativeProject(input = {}) {
|
|
513
|
+
const sources = input.sources ?? [];
|
|
514
|
+
const adapters = input.adapters ?? [];
|
|
515
|
+
const imports = [];
|
|
516
|
+
for (const [index, source] of sources.entries()) {
|
|
517
|
+
const adapter = source.adapter && typeof source.adapter === 'object'
|
|
518
|
+
? source.adapter
|
|
519
|
+
: resolveNativeProjectAdapter(source, adapters, input);
|
|
520
|
+
if (adapter) {
|
|
521
|
+
imports.push(await runNativeImporterAdapter(adapter, {
|
|
522
|
+
...source,
|
|
523
|
+
adapterOptions: source.adapterOptions ?? input.adapterOptions,
|
|
524
|
+
adapterMetadata: {
|
|
525
|
+
projectImportId: input.id,
|
|
526
|
+
sourceIndex: index,
|
|
527
|
+
...input.adapterMetadata,
|
|
528
|
+
...source.adapterMetadata
|
|
529
|
+
}
|
|
530
|
+
}));
|
|
531
|
+
} else {
|
|
532
|
+
imports.push(importNativeSource(source));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return createNativeProjectImportResult(input, imports);
|
|
536
|
+
}
|
|
537
|
+
|
|
139
538
|
export async function runNativeImporterAdapter(adapter, input = {}) {
|
|
140
539
|
const summary = normalizeNativeImporterAdapter(adapter);
|
|
141
540
|
const language = input.language ?? summary.language;
|
|
@@ -255,11 +654,91 @@ export async function runNativeImporterAdapter(adapter, input = {}) {
|
|
|
255
654
|
};
|
|
256
655
|
}
|
|
257
656
|
|
|
657
|
+
export function projectNativeImportToSource(importResult, options = {}) {
|
|
658
|
+
if (!importResult || typeof importResult !== 'object') {
|
|
659
|
+
throw new Error('projectNativeImportToSource requires a native import result');
|
|
660
|
+
}
|
|
661
|
+
const context = nativeImportProjectionContext(importResult, options);
|
|
662
|
+
const candidateSource = nativeProjectionSourceCandidate(context, options);
|
|
663
|
+
const declarations = nativeProjectionDeclarations(importResult, context);
|
|
664
|
+
const preserveSource = options.preferPreservedSource !== false && candidateSource?.exact === true;
|
|
665
|
+
const mode = preserveSource ? 'preserved-source' : 'native-source-stubs';
|
|
666
|
+
const sourceText = preserveSource
|
|
667
|
+
? candidateSource.sourceText
|
|
668
|
+
: renderNativeProjectionStubs(context, declarations, options);
|
|
669
|
+
const losses = preserveSource ? [] : nativeProjectionStubLosses(context, candidateSource, declarations, options);
|
|
670
|
+
const evidence = [{
|
|
671
|
+
id: options.evidenceId ?? `evidence_${context.idPart}_native_source_projection`,
|
|
672
|
+
kind: 'projection',
|
|
673
|
+
status: losses.some((loss) => loss.severity === 'error') ? 'failed' : 'passed',
|
|
674
|
+
path: context.sourcePath,
|
|
675
|
+
summary: preserveSource
|
|
676
|
+
? `Preserved exact ${context.language} source for native projection.`
|
|
677
|
+
: `Projected ${context.language} native import to ${declarations.length} declaration stub(s).`,
|
|
678
|
+
metadata: {
|
|
679
|
+
mode,
|
|
680
|
+
language: context.language,
|
|
681
|
+
sourcePath: context.sourcePath,
|
|
682
|
+
expectedSourceHash: context.sourceHash,
|
|
683
|
+
providedSourceHash: candidateSource?.sourceHash,
|
|
684
|
+
sourceHashVerified: candidateSource?.hashVerified ?? false,
|
|
685
|
+
declarationCount: declarations.length
|
|
686
|
+
}
|
|
687
|
+
}];
|
|
688
|
+
const lossSummary = summarizeNativeImportLosses(losses, {
|
|
689
|
+
evidence,
|
|
690
|
+
parser: context.parser,
|
|
691
|
+
scanKind: 'native-source-projection',
|
|
692
|
+
semanticStatus: context.semanticStatus
|
|
693
|
+
});
|
|
694
|
+
const readiness = classifyNativeImportReadiness(losses, {
|
|
695
|
+
evidence,
|
|
696
|
+
parser: context.parser,
|
|
697
|
+
scanKind: 'native-source-projection',
|
|
698
|
+
semanticStatus: context.semanticStatus
|
|
699
|
+
});
|
|
700
|
+
const nativeImportLossSummary = importResult.metadata?.nativeImportLossSummary ?? summarizeNativeImportLosses(importResult.losses ?? context.nativeAst?.losses ?? [], {
|
|
701
|
+
evidence: importResult.evidence,
|
|
702
|
+
parser: context.parser,
|
|
703
|
+
semanticStatus: context.semanticStatus
|
|
704
|
+
});
|
|
705
|
+
return {
|
|
706
|
+
kind: 'frontier.lang.nativeSourceProjection',
|
|
707
|
+
version: 1,
|
|
708
|
+
id: options.id ?? `native_source_projection_${context.idPart}`,
|
|
709
|
+
language: context.language,
|
|
710
|
+
sourcePath: context.sourcePath,
|
|
711
|
+
sourceHash: context.sourceHash,
|
|
712
|
+
mode,
|
|
713
|
+
sourceText,
|
|
714
|
+
outputHash: hashSemanticValue(sourceText),
|
|
715
|
+
declarations,
|
|
716
|
+
losses,
|
|
717
|
+
lossSummary,
|
|
718
|
+
readiness,
|
|
719
|
+
evidence,
|
|
720
|
+
metadata: {
|
|
721
|
+
nativeImportId: importResult.id,
|
|
722
|
+
nativeSourceId: context.nativeSource?.id,
|
|
723
|
+
nativeAstId: context.nativeAst?.id,
|
|
724
|
+
semanticIndexId: context.semanticIndex?.id,
|
|
725
|
+
universalAstId: importResult.universalAst?.id,
|
|
726
|
+
exactSourceAvailable: candidateSource?.exact === true,
|
|
727
|
+
sourceTextAvailable: typeof candidateSource?.sourceText === 'string',
|
|
728
|
+
sourceHashVerified: candidateSource?.hashVerified ?? false,
|
|
729
|
+
nativeImportLossSummary,
|
|
730
|
+
...options.metadata
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
258
735
|
export function importNativeSource(input) {
|
|
259
736
|
const language = input.language ?? input.nativeAst?.language;
|
|
260
737
|
if (!language) throw new Error('importNativeSource requires a language or nativeAst.language');
|
|
261
738
|
const sourcePath = input.sourcePath ?? input.nativeAst?.sourcePath;
|
|
262
739
|
const sourceHash = input.sourceHash ?? input.nativeAst?.sourceHash ?? (input.sourceText ? hashSemanticValue(input.sourceText) : hashSemanticValue(input.nativeAst?.nodes ?? input.nativeAst ?? {}));
|
|
740
|
+
const targetPath = input.targetPath ?? input.target?.emitPath;
|
|
741
|
+
const targetHash = input.targetHash;
|
|
263
742
|
const importIdPart = idFragment(input.id ?? input.nativeSourceId ?? sourcePath ?? language);
|
|
264
743
|
const lightweight = !input.nativeAst && !input.nodes && input.sourceText
|
|
265
744
|
? createLightweightNativeImport({
|
|
@@ -295,7 +774,9 @@ export function importNativeSource(input) {
|
|
|
295
774
|
}
|
|
296
775
|
});
|
|
297
776
|
const frontierNodeIds = input.frontierNodeIds ?? input.semanticNodes?.map((node) => node.id) ?? [];
|
|
298
|
-
const
|
|
777
|
+
const semanticNodes = input.semanticNodes ?? [];
|
|
778
|
+
const semanticStatus = input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only');
|
|
779
|
+
const losses = normalizeNativeLossRecords(input.losses ?? nativeAst.losses ?? lightweight?.losses ?? []);
|
|
299
780
|
const nativeSource = nativeSourceNode({
|
|
300
781
|
id: input.nativeSourceId ?? `native_source_${importIdPart}`,
|
|
301
782
|
name: input.name ?? sourcePath?.split(/[\\/]/).filter(Boolean).at(-1) ?? `${language}NativeSource`,
|
|
@@ -310,12 +791,11 @@ export function importNativeSource(input) {
|
|
|
310
791
|
losses,
|
|
311
792
|
target: input.target,
|
|
312
793
|
metadata: {
|
|
313
|
-
semanticStatus
|
|
794
|
+
semanticStatus,
|
|
314
795
|
mappings: input.mappings ?? [],
|
|
315
796
|
...input.nativeSourceMetadata
|
|
316
797
|
}
|
|
317
798
|
});
|
|
318
|
-
const semanticNodes = input.semanticNodes ?? [];
|
|
319
799
|
const document = createDocument({
|
|
320
800
|
id: input.documentId ?? `document_${importIdPart}`,
|
|
321
801
|
name: input.documentName ?? nativeSource.name,
|
|
@@ -323,11 +803,11 @@ export function importNativeSource(input) {
|
|
|
323
803
|
rootIds: input.rootIds,
|
|
324
804
|
metadata: {
|
|
325
805
|
sourceLanguage: language,
|
|
326
|
-
semanticStatus
|
|
806
|
+
semanticStatus,
|
|
327
807
|
...input.documentMetadata
|
|
328
808
|
}
|
|
329
809
|
});
|
|
330
|
-
const
|
|
810
|
+
const baseEvidence = input.evidence ?? [{
|
|
331
811
|
id: input.evidenceId ?? `evidence_${importIdPart}_import`,
|
|
332
812
|
kind: 'import',
|
|
333
813
|
status: losses.some((loss) => loss.severity === 'error') ? 'failed' : 'passed',
|
|
@@ -336,9 +816,17 @@ export function importNativeSource(input) {
|
|
|
336
816
|
metadata: {
|
|
337
817
|
parser: nativeAst.parser,
|
|
338
818
|
sourcePath,
|
|
339
|
-
semanticStatus
|
|
819
|
+
semanticStatus
|
|
340
820
|
}
|
|
341
821
|
}];
|
|
822
|
+
const lossSummary = summarizeNativeImportLosses(losses, {
|
|
823
|
+
exactAst: Boolean(input.nativeAst || input.nodes),
|
|
824
|
+
evidence: baseEvidence,
|
|
825
|
+
parser: nativeAst.parser,
|
|
826
|
+
scanKind: lightweight?.metadata?.scanKind,
|
|
827
|
+
semanticStatus
|
|
828
|
+
});
|
|
829
|
+
const evidence = attachNativeImportLossSummary(baseEvidence, lossSummary);
|
|
342
830
|
const semanticIndex = input.semanticIndex ?? lightweight?.semanticIndex;
|
|
343
831
|
const sourceMapMappings = normalizeSourceMapMappings(
|
|
344
832
|
input.mappings ?? lightweight?.mappings ?? inferSourceMapMappings({
|
|
@@ -353,16 +841,20 @@ export function importNativeSource(input) {
|
|
|
353
841
|
nativeSource,
|
|
354
842
|
evidence,
|
|
355
843
|
losses,
|
|
356
|
-
target: input.target
|
|
844
|
+
target: input.target,
|
|
845
|
+
targetPath,
|
|
846
|
+
targetHash
|
|
357
847
|
}
|
|
358
848
|
);
|
|
849
|
+
const inferredTargetPath = targetPath ?? commonGeneratedTargetPath(sourceMapMappings);
|
|
359
850
|
const inferredSourceMaps = sourceMapMappings.length
|
|
360
851
|
? [createSourceMapRecord({
|
|
361
852
|
id: input.sourceMapId ?? `source_map_${importIdPart}`,
|
|
362
853
|
sourcePath,
|
|
363
854
|
sourceHash,
|
|
364
855
|
target: input.target,
|
|
365
|
-
targetPath:
|
|
856
|
+
targetPath: inferredTargetPath,
|
|
857
|
+
targetHash,
|
|
366
858
|
semanticIndexId: semanticIndex?.id,
|
|
367
859
|
nativeAstId: nativeAst.id,
|
|
368
860
|
nativeSourceId: nativeSource.id,
|
|
@@ -371,11 +863,26 @@ export function importNativeSource(input) {
|
|
|
371
863
|
metadata: {
|
|
372
864
|
sourceLanguage: language,
|
|
373
865
|
parser: nativeAst.parser,
|
|
374
|
-
semanticStatus
|
|
866
|
+
semanticStatus
|
|
375
867
|
}
|
|
376
868
|
})]
|
|
377
869
|
: [];
|
|
378
|
-
const sourceMaps = input.sourceMaps ?? inferredSourceMaps
|
|
870
|
+
const sourceMaps = normalizeSourceMaps(input.sourceMaps ?? inferredSourceMaps, {
|
|
871
|
+
document,
|
|
872
|
+
nativeSources: [nativeSource],
|
|
873
|
+
nativeAst,
|
|
874
|
+
nativeSource,
|
|
875
|
+
semanticIndex,
|
|
876
|
+
evidence,
|
|
877
|
+
losses,
|
|
878
|
+
target: input.target,
|
|
879
|
+
targetPath: inferredTargetPath,
|
|
880
|
+
targetHash,
|
|
881
|
+
sourcePath,
|
|
882
|
+
sourceHash,
|
|
883
|
+
defaultSourceMapId: `source_map_${importIdPart}`
|
|
884
|
+
});
|
|
885
|
+
const resultSourceMapMappings = sourceMaps.flatMap((sourceMap) => sourceMap.mappings ?? []);
|
|
379
886
|
const universalAst = createUniversalAstEnvelope({
|
|
380
887
|
id: input.universalAstId ?? `universal_ast_${importIdPart}`,
|
|
381
888
|
document,
|
|
@@ -387,7 +894,8 @@ export function importNativeSource(input) {
|
|
|
387
894
|
metadata: {
|
|
388
895
|
sourceLanguage: language,
|
|
389
896
|
sourcePath,
|
|
390
|
-
semanticStatus
|
|
897
|
+
semanticStatus,
|
|
898
|
+
nativeImportLossSummary: lossSummary,
|
|
391
899
|
...input.universalAstMetadata
|
|
392
900
|
}
|
|
393
901
|
});
|
|
@@ -406,32 +914,35 @@ export function importNativeSource(input) {
|
|
|
406
914
|
sourcePath,
|
|
407
915
|
semanticIndexId: semanticIndex?.id,
|
|
408
916
|
universalAstId: universalAst.id,
|
|
409
|
-
sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id)
|
|
917
|
+
sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
|
|
918
|
+
nativeImportLossSummary: lossSummary
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
const importResult = createImportResult({
|
|
922
|
+
id: input.id ?? `import_${importIdPart}`,
|
|
923
|
+
language,
|
|
924
|
+
sourcePath,
|
|
925
|
+
document,
|
|
926
|
+
patch,
|
|
927
|
+
nativeAst,
|
|
928
|
+
semanticIndex,
|
|
929
|
+
universalAst,
|
|
930
|
+
sourceMaps,
|
|
931
|
+
losses,
|
|
932
|
+
evidence,
|
|
933
|
+
metadata: {
|
|
934
|
+
nativeSourceId: nativeSource.id,
|
|
935
|
+
semanticIndexId: semanticIndex?.id,
|
|
936
|
+
universalAstId: universalAst.id,
|
|
937
|
+
sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
|
|
938
|
+
semanticStatus,
|
|
939
|
+
mappings: resultSourceMapMappings,
|
|
940
|
+
nativeImportLossSummary: lossSummary,
|
|
941
|
+
...input.metadata
|
|
410
942
|
}
|
|
411
943
|
});
|
|
412
944
|
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
|
-
}),
|
|
945
|
+
...withNativeImportReadiness(importResult, lossSummary),
|
|
435
946
|
nativeSource
|
|
436
947
|
};
|
|
437
948
|
}
|
|
@@ -459,6 +970,7 @@ function createLightweightNativeImport(input) {
|
|
|
459
970
|
const evidenceId = `evidence_${idFragment(input.sourcePath ?? input.language)}_lightweight_scan`;
|
|
460
971
|
|
|
461
972
|
for (const declaration of declarations) {
|
|
973
|
+
const ownershipRegion = semanticOwnershipRegionForDeclaration(input, declaration, documentId);
|
|
462
974
|
nodes[rootId].children.push(declaration.nodeId);
|
|
463
975
|
nodes[declaration.nodeId] = {
|
|
464
976
|
id: declaration.nodeId,
|
|
@@ -467,7 +979,11 @@ function createLightweightNativeImport(input) {
|
|
|
467
979
|
span: declaration.span,
|
|
468
980
|
value: declaration.name ?? declaration.importPath ?? null,
|
|
469
981
|
fields: declaration.fields,
|
|
470
|
-
metadata:
|
|
982
|
+
metadata: {
|
|
983
|
+
...declaration.metadata,
|
|
984
|
+
ownershipRegionId: ownershipRegion.id,
|
|
985
|
+
ownershipRegionKey: ownershipRegion.key
|
|
986
|
+
}
|
|
471
987
|
};
|
|
472
988
|
if (declaration.symbolId) {
|
|
473
989
|
const occurrenceId = `occ_${idFragment(declaration.nodeId)}_def`;
|
|
@@ -479,7 +995,11 @@ function createLightweightNativeImport(input) {
|
|
|
479
995
|
language: input.language,
|
|
480
996
|
nativeAstNodeId: declaration.nodeId,
|
|
481
997
|
signatureHash: hashSemanticValue([input.language, declaration.kind, declaration.name, declaration.fields ?? {}]),
|
|
482
|
-
definitionSpan: declaration.span
|
|
998
|
+
definitionSpan: declaration.span,
|
|
999
|
+
metadata: {
|
|
1000
|
+
ownershipRegionId: ownershipRegion.id,
|
|
1001
|
+
ownershipRegionKey: ownershipRegion.key
|
|
1002
|
+
}
|
|
483
1003
|
});
|
|
484
1004
|
occurrences.push({
|
|
485
1005
|
id: occurrenceId,
|
|
@@ -500,6 +1020,11 @@ function createLightweightNativeImport(input) {
|
|
|
500
1020
|
predicate: 'nativeKind',
|
|
501
1021
|
subjectId: declaration.symbolId,
|
|
502
1022
|
value: declaration.languageKind
|
|
1023
|
+
}, {
|
|
1024
|
+
id: `fact_${idFragment(declaration.nodeId)}_ownership_region`,
|
|
1025
|
+
predicate: 'semanticOwnershipRegion',
|
|
1026
|
+
subjectId: declaration.symbolId,
|
|
1027
|
+
value: ownershipRegion
|
|
503
1028
|
});
|
|
504
1029
|
mappings.push({
|
|
505
1030
|
id: `map_${idFragment(declaration.nodeId)}`,
|
|
@@ -509,6 +1034,7 @@ function createLightweightNativeImport(input) {
|
|
|
509
1034
|
sourceSpan: declaration.span,
|
|
510
1035
|
evidenceIds: [evidenceId],
|
|
511
1036
|
lossIds: declaration.loss ? [declaration.loss.id] : [],
|
|
1037
|
+
ownershipRegionId: ownershipRegion.id,
|
|
512
1038
|
precision: 'declaration'
|
|
513
1039
|
});
|
|
514
1040
|
}
|
|
@@ -554,6 +1080,314 @@ function createLightweightNativeImport(input) {
|
|
|
554
1080
|
};
|
|
555
1081
|
}
|
|
556
1082
|
|
|
1083
|
+
function nativeImportProjectionContext(importResult, options) {
|
|
1084
|
+
const nativeSource = options.nativeSource
|
|
1085
|
+
?? importResult.nativeSource
|
|
1086
|
+
?? importResult.nativeSources?.[0]
|
|
1087
|
+
?? importResult.universalAst?.nativeSources?.[0];
|
|
1088
|
+
const nativeAst = options.nativeAst
|
|
1089
|
+
?? importResult.nativeAst
|
|
1090
|
+
?? nativeSource?.ast
|
|
1091
|
+
?? importResult.universalAst?.nativeSources?.[0]?.ast;
|
|
1092
|
+
const semanticIndex = options.semanticIndex
|
|
1093
|
+
?? importResult.semanticIndex
|
|
1094
|
+
?? importResult.universalAst?.semanticIndex;
|
|
1095
|
+
const language = options.language
|
|
1096
|
+
?? importResult.language
|
|
1097
|
+
?? nativeSource?.language
|
|
1098
|
+
?? nativeAst?.language
|
|
1099
|
+
?? importResult.universalAst?.metadata?.sourceLanguage
|
|
1100
|
+
?? 'source';
|
|
1101
|
+
const sourcePath = options.sourcePath
|
|
1102
|
+
?? importResult.sourcePath
|
|
1103
|
+
?? nativeSource?.sourcePath
|
|
1104
|
+
?? nativeAst?.sourcePath
|
|
1105
|
+
?? importResult.universalAst?.metadata?.sourcePath;
|
|
1106
|
+
const sourceHash = options.expectedSourceHash
|
|
1107
|
+
?? importResult.sourceHash
|
|
1108
|
+
?? nativeSource?.sourceHash
|
|
1109
|
+
?? nativeAst?.sourceHash;
|
|
1110
|
+
return {
|
|
1111
|
+
nativeSource,
|
|
1112
|
+
nativeAst,
|
|
1113
|
+
semanticIndex,
|
|
1114
|
+
language,
|
|
1115
|
+
sourcePath,
|
|
1116
|
+
sourceHash,
|
|
1117
|
+
parser: options.parser ?? nativeAst?.parser ?? nativeSource?.parser,
|
|
1118
|
+
semanticStatus: options.semanticStatus ?? importResult.metadata?.semanticStatus ?? nativeSource?.metadata?.semanticStatus,
|
|
1119
|
+
idPart: idFragment(options.id ?? importResult.id ?? nativeSource?.id ?? sourcePath ?? language)
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function nativeProjectionSourceCandidate(context, options) {
|
|
1124
|
+
const sourceText = options.sourceText ?? options.preservedSourceText ?? options.exactSourceText;
|
|
1125
|
+
if (typeof sourceText !== 'string') return undefined;
|
|
1126
|
+
const sourceHash = options.sourceHash ?? hashSemanticValue(sourceText);
|
|
1127
|
+
const hashVerified = Boolean(context.sourceHash);
|
|
1128
|
+
const exact = !context.sourceHash || sourceHash === context.sourceHash || options.verifySourceHash === false;
|
|
1129
|
+
return {
|
|
1130
|
+
sourceText,
|
|
1131
|
+
sourceHash,
|
|
1132
|
+
hashVerified,
|
|
1133
|
+
exact,
|
|
1134
|
+
mismatch: hashVerified && sourceHash !== context.sourceHash && options.verifySourceHash !== false
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function nativeProjectionDeclarations(importResult, context) {
|
|
1139
|
+
const semanticIndex = context.semanticIndex;
|
|
1140
|
+
const occurrencesBySymbol = new Map();
|
|
1141
|
+
for (const occurrence of semanticIndex?.occurrences ?? []) {
|
|
1142
|
+
const list = occurrencesBySymbol.get(occurrence.symbolId) ?? [];
|
|
1143
|
+
list.push(occurrence);
|
|
1144
|
+
occurrencesBySymbol.set(occurrence.symbolId, list);
|
|
1145
|
+
}
|
|
1146
|
+
const declarations = (semanticIndex?.symbols ?? [])
|
|
1147
|
+
.filter((symbol) => !nativeProjectionImportOnlySymbol(symbol, occurrencesBySymbol.get(symbol.id)))
|
|
1148
|
+
.map((symbol) => {
|
|
1149
|
+
const occurrence = occurrencesBySymbol.get(symbol.id)?.find((item) => item.role !== 'import');
|
|
1150
|
+
const mapping = (importResult.sourceMaps ?? importResult.universalAst?.sourceMaps ?? [])
|
|
1151
|
+
.flatMap((sourceMap) => sourceMap.mappings ?? [])
|
|
1152
|
+
.find((item) => item.semanticSymbolId === symbol.id);
|
|
1153
|
+
return {
|
|
1154
|
+
name: symbol.name,
|
|
1155
|
+
kind: nativeProjectionDeclarationKind(symbol.kind),
|
|
1156
|
+
symbolId: symbol.id,
|
|
1157
|
+
nativeAstNodeId: symbol.nativeAstNodeId ?? occurrence?.nativeAstNodeId,
|
|
1158
|
+
sourceSpan: symbol.definitionSpan ?? occurrence?.span ?? mapping?.sourceSpan,
|
|
1159
|
+
ownershipRegionId: mapping?.ownershipRegionId ?? symbol.metadata?.ownershipRegionId,
|
|
1160
|
+
metadata: {
|
|
1161
|
+
semanticKind: symbol.kind,
|
|
1162
|
+
language: symbol.language,
|
|
1163
|
+
signatureHash: symbol.signatureHash
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
})
|
|
1167
|
+
.filter((declaration) => declaration.name);
|
|
1168
|
+
if (declarations.length) return uniqueNativeProjectionDeclarations(declarations);
|
|
1169
|
+
return uniqueNativeProjectionDeclarations(Object.values(context.nativeAst?.nodes ?? {})
|
|
1170
|
+
.map((node) => {
|
|
1171
|
+
const name = typeof node.value === 'string' && node.value.trim() ? node.value.trim() : node.fields?.name;
|
|
1172
|
+
const kind = nativeProjectionKindForNode(node);
|
|
1173
|
+
if (!name || !kind) return undefined;
|
|
1174
|
+
return {
|
|
1175
|
+
name,
|
|
1176
|
+
kind,
|
|
1177
|
+
nativeAstNodeId: node.id,
|
|
1178
|
+
sourceSpan: node.span,
|
|
1179
|
+
ownershipRegionId: node.metadata?.ownershipRegionId,
|
|
1180
|
+
metadata: { nativeKind: node.kind, language: context.language }
|
|
1181
|
+
};
|
|
1182
|
+
})
|
|
1183
|
+
.filter(Boolean));
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function nativeProjectionImportOnlySymbol(symbol, occurrences = []) {
|
|
1187
|
+
if (String(symbol.id ?? '').includes(':import:')) return true;
|
|
1188
|
+
if (occurrences.length && occurrences.every((occurrence) => occurrence.role === 'import')) return true;
|
|
1189
|
+
return symbol.kind === 'module' && occurrences.some((occurrence) => occurrence.role === 'import');
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
function nativeProjectionDeclarationKind(kind) {
|
|
1193
|
+
const normalized = String(kind ?? 'value').toLowerCase();
|
|
1194
|
+
if (normalized === 'function' || normalized === 'method' || normalized === 'procedure') return 'function';
|
|
1195
|
+
if (normalized === 'class') return 'class';
|
|
1196
|
+
if (normalized === 'interface' || normalized === 'protocol') return 'interface';
|
|
1197
|
+
if (normalized === 'trait') return 'trait';
|
|
1198
|
+
if (normalized === 'type' || normalized === 'struct' || normalized === 'enum' || normalized === 'record') return 'type';
|
|
1199
|
+
if (normalized === 'constant' || normalized === 'const') return 'constant';
|
|
1200
|
+
if (normalized === 'variable' || normalized === 'property' || normalized === 'field') return 'variable';
|
|
1201
|
+
if (normalized === 'module' || normalized === 'namespace' || normalized === 'package') return 'module';
|
|
1202
|
+
return normalized;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function nativeProjectionKindForNode(node) {
|
|
1206
|
+
const kind = String(node?.kind ?? node?.languageKind ?? '').toLowerCase();
|
|
1207
|
+
if (/function|method|procedure|funcdecl|itemfn|fndeclaration|\bdef\b/.test(kind)) return 'function';
|
|
1208
|
+
if (/class/.test(kind)) return 'class';
|
|
1209
|
+
if (/interface|protocol/.test(kind)) return 'interface';
|
|
1210
|
+
if (/trait/.test(kind)) return 'trait';
|
|
1211
|
+
if (/struct|enum|record|typedef|typealias|type/.test(kind)) return 'type';
|
|
1212
|
+
if (/const|constant|macro|define/.test(kind)) return 'constant';
|
|
1213
|
+
if (/var|property|field/.test(kind)) return 'variable';
|
|
1214
|
+
if (/module|namespace|package/.test(kind)) return 'module';
|
|
1215
|
+
return undefined;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function uniqueNativeProjectionDeclarations(declarations) {
|
|
1219
|
+
const seen = new Set();
|
|
1220
|
+
return declarations.filter((declaration) => {
|
|
1221
|
+
const key = `${declaration.kind}:${declaration.name}:${declaration.symbolId ?? declaration.nativeAstNodeId ?? ''}`;
|
|
1222
|
+
if (seen.has(key)) return false;
|
|
1223
|
+
seen.add(key);
|
|
1224
|
+
return true;
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function nativeProjectionStubLosses(context, candidateSource, declarations, options) {
|
|
1229
|
+
const reason = candidateSource?.mismatch
|
|
1230
|
+
? 'source-hash-mismatch'
|
|
1231
|
+
: options.preferPreservedSource === false && candidateSource?.sourceText
|
|
1232
|
+
? 'preserved-source-disabled'
|
|
1233
|
+
: 'exact-source-unavailable';
|
|
1234
|
+
const message = candidateSource?.mismatch
|
|
1235
|
+
? 'Provided native source text hash did not match the import result source hash; emitted declaration stubs instead of preserving stale source.'
|
|
1236
|
+
: options.preferPreservedSource === false && candidateSource?.sourceText
|
|
1237
|
+
? 'Native source projection was asked to emit declaration stubs instead of preserving available source text.'
|
|
1238
|
+
: 'Exact native source text was not provided; emitted declaration stubs reconstructed from import metadata.';
|
|
1239
|
+
const losses = [nativeProjectionLoss(context, {
|
|
1240
|
+
id: `loss_${context.idPart}_native_source_stub`,
|
|
1241
|
+
kind: 'sourcePreservation',
|
|
1242
|
+
severity: 'warning',
|
|
1243
|
+
message,
|
|
1244
|
+
metadata: {
|
|
1245
|
+
reason,
|
|
1246
|
+
projectionMode: 'native-source-stubs',
|
|
1247
|
+
expectedSourceHash: context.sourceHash,
|
|
1248
|
+
providedSourceHash: candidateSource?.sourceHash
|
|
1249
|
+
}
|
|
1250
|
+
})];
|
|
1251
|
+
if (!declarations.length) {
|
|
1252
|
+
losses.push(nativeProjectionLoss(context, {
|
|
1253
|
+
id: `loss_${context.idPart}_native_source_stub_empty`,
|
|
1254
|
+
kind: 'declarationOnlyCoverage',
|
|
1255
|
+
severity: 'warning',
|
|
1256
|
+
message: 'Native import result did not expose semantic declarations for source stub generation.',
|
|
1257
|
+
metadata: { reason: 'no-stub-declarations', projectionMode: 'native-source-stubs' }
|
|
1258
|
+
}));
|
|
1259
|
+
}
|
|
1260
|
+
return losses;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function nativeProjectionLoss(context, input) {
|
|
1264
|
+
const rootSpan = context.nativeAst?.nodes?.[context.nativeAst.rootId]?.span;
|
|
1265
|
+
return {
|
|
1266
|
+
id: input.id,
|
|
1267
|
+
severity: input.severity,
|
|
1268
|
+
phase: 'emit',
|
|
1269
|
+
sourceFormat: context.language,
|
|
1270
|
+
kind: input.kind,
|
|
1271
|
+
message: input.message,
|
|
1272
|
+
span: rootSpan ?? {
|
|
1273
|
+
sourceId: context.sourceHash,
|
|
1274
|
+
path: context.sourcePath,
|
|
1275
|
+
startLine: 1,
|
|
1276
|
+
startColumn: 1
|
|
1277
|
+
},
|
|
1278
|
+
metadata: {
|
|
1279
|
+
nativeSourceId: context.nativeSource?.id,
|
|
1280
|
+
nativeAstId: context.nativeAst?.id,
|
|
1281
|
+
parser: context.parser,
|
|
1282
|
+
...input.metadata
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
function renderNativeProjectionStubs(context, declarations, options) {
|
|
1288
|
+
const language = String(context.language ?? 'source').toLowerCase();
|
|
1289
|
+
const header = nativeProjectionStubHeader(language, context, options);
|
|
1290
|
+
let body;
|
|
1291
|
+
if (language === 'typescript' || language === 'ts') body = renderTypeScriptProjectionStubs(declarations);
|
|
1292
|
+
else if (language === 'javascript' || language === 'js') body = renderJavaScriptProjectionStubs(declarations);
|
|
1293
|
+
else if (language === 'python' || language === 'py') body = renderPythonProjectionStubs(declarations);
|
|
1294
|
+
else if (language === 'rust' || language === 'rs') body = renderRustProjectionStubs(declarations);
|
|
1295
|
+
else if (language === 'c' || language === 'cpp' || language === 'c++' || language === 'h') body = renderCProjectionStubs(declarations);
|
|
1296
|
+
else body = renderGenericProjectionStubs(declarations, language);
|
|
1297
|
+
return ensureTrailingNewline([header, body].filter(Boolean).join('\n'));
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
function nativeProjectionStubHeader(language, context, options) {
|
|
1301
|
+
if (options.stubBanner === false) return '';
|
|
1302
|
+
if (typeof options.stubBanner === 'string') return ensureTrailingNewline(options.stubBanner).trimEnd();
|
|
1303
|
+
const comment = nativeProjectionLineComment(language);
|
|
1304
|
+
const suffix = context.sourcePath ? ` for ${oneLine(context.sourcePath)}` : '';
|
|
1305
|
+
return [
|
|
1306
|
+
`${comment} Frontier native source stubs${suffix}`,
|
|
1307
|
+
`${comment} Exact source text was unavailable; declarations are reconstructed from native import metadata.`
|
|
1308
|
+
].join('\n');
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function nativeProjectionLineComment(language) {
|
|
1312
|
+
if (language === 'python' || language === 'py' || language === 'ruby' || language === 'rb' || language === 'shell' || language === 'sh') return '#';
|
|
1313
|
+
if (language === 'sql') return '--';
|
|
1314
|
+
return '//';
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function renderJavaScriptProjectionStubs(declarations) {
|
|
1318
|
+
const used = new Set();
|
|
1319
|
+
if (!declarations.length) return 'export {};';
|
|
1320
|
+
return declarations.map((declaration) => {
|
|
1321
|
+
const name = reserveUniqueId(safeProjectionIdentifier(declaration.name), used);
|
|
1322
|
+
if (declaration.kind === 'function') return `export function ${name}(...args) {\n throw new Error('Frontier native source stub: implementation unavailable.');\n}`;
|
|
1323
|
+
if (declaration.kind === 'class') return `export class ${name} {}`;
|
|
1324
|
+
return `export const ${name} = undefined;`;
|
|
1325
|
+
}).join('\n\n');
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function renderTypeScriptProjectionStubs(declarations) {
|
|
1329
|
+
const used = new Set();
|
|
1330
|
+
if (!declarations.length) return 'export {};';
|
|
1331
|
+
return declarations.map((declaration) => {
|
|
1332
|
+
const name = reserveUniqueId(safeProjectionIdentifier(declaration.name), used);
|
|
1333
|
+
if (declaration.kind === 'function') return `export function ${name}(...args: unknown[]): never {\n throw new Error('Frontier native source stub: implementation unavailable.');\n}`;
|
|
1334
|
+
if (declaration.kind === 'class') return `export class ${name} {}`;
|
|
1335
|
+
if (declaration.kind === 'interface') return `export interface ${name} {}`;
|
|
1336
|
+
if (declaration.kind === 'type' || declaration.kind === 'trait') return `export type ${name} = unknown;`;
|
|
1337
|
+
return `export const ${name}: unknown = undefined;`;
|
|
1338
|
+
}).join('\n\n');
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function renderPythonProjectionStubs(declarations) {
|
|
1342
|
+
if (!declarations.length) return 'pass';
|
|
1343
|
+
return declarations.map((declaration) => {
|
|
1344
|
+
const name = safeProjectionIdentifier(declaration.name);
|
|
1345
|
+
if (declaration.kind === 'function') return `def ${name}(*args, **kwargs):\n raise NotImplementedError("Frontier native source stub")`;
|
|
1346
|
+
if (declaration.kind === 'class') return `class ${name}:\n pass`;
|
|
1347
|
+
return `${name} = None`;
|
|
1348
|
+
}).join('\n\n');
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function renderRustProjectionStubs(declarations) {
|
|
1352
|
+
if (!declarations.length) return '';
|
|
1353
|
+
return declarations.map((declaration) => {
|
|
1354
|
+
const name = safeProjectionIdentifier(declaration.name);
|
|
1355
|
+
if (declaration.kind === 'function') return `pub fn ${name}() {\n unimplemented!(\"Frontier native source stub\");\n}`;
|
|
1356
|
+
if (declaration.kind === 'type' || declaration.kind === 'class') return `pub struct ${upperFirst(name)};`;
|
|
1357
|
+
return `pub const ${name.toUpperCase()}: () = ();`;
|
|
1358
|
+
}).join('\n\n');
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function renderCProjectionStubs(declarations) {
|
|
1362
|
+
if (!declarations.length) return '';
|
|
1363
|
+
return declarations.map((declaration) => {
|
|
1364
|
+
const name = safeProjectionIdentifier(declaration.name);
|
|
1365
|
+
if (declaration.kind === 'function') return `void ${name}(void);`;
|
|
1366
|
+
if (declaration.kind === 'type' || declaration.kind === 'class') return `typedef struct ${upperFirst(name)} ${upperFirst(name)};`;
|
|
1367
|
+
return `extern const int ${name};`;
|
|
1368
|
+
}).join('\n');
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
function renderGenericProjectionStubs(declarations, language) {
|
|
1372
|
+
if (!declarations.length) return `${nativeProjectionLineComment(language)} no declarations available`;
|
|
1373
|
+
return declarations.map((declaration) => `${declaration.kind} ${declaration.name}`).join('\n');
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function safeProjectionIdentifier(name) {
|
|
1377
|
+
const text = String(name ?? 'value').split('.').at(-1).replace(/[^A-Za-z0-9_$]/g, '_');
|
|
1378
|
+
const identifier = text || 'value';
|
|
1379
|
+
return /^[A-Za-z_$]/.test(identifier) ? identifier : `_${identifier}`;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function ensureTrailingNewline(value) {
|
|
1383
|
+
const text = String(value ?? '');
|
|
1384
|
+
return text.endsWith('\n') ? text : `${text}\n`;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
function oneLine(value) {
|
|
1388
|
+
return String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
1389
|
+
}
|
|
1390
|
+
|
|
557
1391
|
function scanNativeDeclarations(input) {
|
|
558
1392
|
const language = String(input.language).toLowerCase();
|
|
559
1393
|
if (language === 'javascript' || language === 'typescript') return scanJavaScriptLike(input);
|
|
@@ -566,28 +1400,79 @@ function scanNativeDeclarations(input) {
|
|
|
566
1400
|
if (language === 'csharp' || language === 'c#') return scanCSharp(input);
|
|
567
1401
|
if (language === 'php') return scanPhp(input);
|
|
568
1402
|
if (language === 'ruby' || language === 'rb') return scanRuby(input);
|
|
1403
|
+
if (language === 'kotlin' || language === 'kt') return scanKotlin(input);
|
|
1404
|
+
if (language === 'scala' || language === 'sc') return scanScala(input);
|
|
1405
|
+
if (language === 'dart') return scanDart(input);
|
|
1406
|
+
if (language === 'lua') return scanLua(input);
|
|
1407
|
+
if (language === 'shell' || language === 'sh' || language === 'bash' || language === 'zsh') return scanShell(input);
|
|
1408
|
+
if (language === 'sql' || language === 'postgresql' || language === 'postgres' || language === 'mysql' || language === 'sqlite') return scanSql(input);
|
|
1409
|
+
if (language === 'zig') return scanZig(input);
|
|
1410
|
+
if (language === 'elixir' || language === 'ex' || language === 'exs') return scanElixir(input);
|
|
1411
|
+
if (language === 'erlang' || language === 'erl' || language === 'hrl') return scanErlang(input);
|
|
1412
|
+
if (language === 'haskell' || language === 'hs') return scanHaskell(input);
|
|
1413
|
+
if (language === 'r') return scanR(input);
|
|
569
1414
|
return scanGenericDeclarations(input);
|
|
570
1415
|
}
|
|
571
1416
|
|
|
572
1417
|
function scanJavaScriptLike(input) {
|
|
573
1418
|
const declarations = [];
|
|
1419
|
+
let currentClass;
|
|
1420
|
+
let classDepth = 0;
|
|
574
1421
|
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
575
1422
|
const trimmed = line.trim();
|
|
1423
|
+
const declarationLine = trimmed.replace(/^(?:export\s+)?(?:declare\s+)?/, '');
|
|
576
1424
|
let match;
|
|
577
|
-
if ((match = trimmed.match(/^
|
|
578
|
-
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
579
|
-
} else if ((match = trimmed.match(/^(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/))) {
|
|
580
|
-
declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'class', match[1], {}, trimmed.includes('{')));
|
|
581
|
-
} else if ((match = trimmed.match(/^(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/))) {
|
|
582
|
-
declarations.push(nativeDeclaration(input, number, 'InterfaceDeclaration', 'interface', match[1], {}, trimmed.includes('{')));
|
|
583
|
-
} else if ((match = trimmed.match(/^(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/))) {
|
|
584
|
-
declarations.push(nativeDeclaration(input, number, 'TypeAliasDeclaration', 'type', match[1], {}, false));
|
|
585
|
-
} else if ((match = trimmed.match(/^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?([^=;]*)\)?\s*=>/))) {
|
|
586
|
-
declarations.push(nativeDeclaration(input, number, 'VariableFunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
|
|
587
|
-
} else if ((match = trimmed.match(/^import\s+(?:.+?\s+from\s+)?['"]([^'"]+)['"]/))) {
|
|
1425
|
+
if ((match = trimmed.match(/^import\s+(?:.+?\s+from\s+)?['"]([^'"]+)['"]/))) {
|
|
588
1426
|
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'module'));
|
|
589
|
-
} else if ((match = trimmed.match(/^
|
|
1427
|
+
} else if ((match = trimmed.match(/^import\s*\(\s*['"]([^'"]+)['"]\s*\)/))) {
|
|
1428
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'DynamicImportExpression', 'module'));
|
|
1429
|
+
} else if ((match = trimmed.match(/^export\s+(?:\*\s+from|\{[^}]*\}\s+from)\s+['"]([^'"]+)['"]/))) {
|
|
590
1430
|
declarations.push(nativeImportDeclaration(input, number, match[1], 'ExportFromDeclaration', 'module'));
|
|
1431
|
+
} else if ((match = declarationLine.match(/^(?:async\s+)?function\*?\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)/))) {
|
|
1432
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, declarationLine.includes('{')));
|
|
1433
|
+
} else if ((match = trimmed.match(/^export\s+default\s+(?:async\s+)?function\*?\s*([A-Za-z_$][\w$]*)?\s*\(([^)]*)\)/))) {
|
|
1434
|
+
declarations.push(nativeDeclaration(input, number, 'ExportDefaultFunctionDeclaration', 'function', match[1] ?? 'default', { parameters: splitParameters(match[2]), exportDefault: true }, trimmed.includes('{')));
|
|
1435
|
+
} else if ((match = declarationLine.match(/^(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)/))) {
|
|
1436
|
+
declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'class', match[1], {}, declarationLine.includes('{')));
|
|
1437
|
+
if (declarationLine.includes('{') && !declarationLine.includes('}')) {
|
|
1438
|
+
currentClass = match[1];
|
|
1439
|
+
classDepth = 0;
|
|
1440
|
+
}
|
|
1441
|
+
} else if ((match = declarationLine.match(/^interface\s+([A-Za-z_$][\w$]*)/))) {
|
|
1442
|
+
declarations.push(nativeDeclaration(input, number, 'InterfaceDeclaration', 'interface', match[1], {}, declarationLine.includes('{')));
|
|
1443
|
+
} else if ((match = declarationLine.match(/^(?:const\s+)?enum\s+([A-Za-z_$][\w$]*)/))) {
|
|
1444
|
+
declarations.push(nativeDeclaration(input, number, 'EnumDeclaration', 'type', match[1], {}, declarationLine.includes('{')));
|
|
1445
|
+
} else if ((match = declarationLine.match(/^(?:namespace|module)\s+([A-Za-z_$][\w$.]*)/))) {
|
|
1446
|
+
declarations.push(nativeDeclaration(input, number, 'ModuleDeclaration', 'module', match[1], {}, declarationLine.includes('{')));
|
|
1447
|
+
} else if ((match = declarationLine.match(/^type\s+([A-Za-z_$][\w$]*)\s*=/))) {
|
|
1448
|
+
declarations.push(nativeDeclaration(input, number, 'TypeAliasDeclaration', 'type', match[1], {}, false));
|
|
1449
|
+
} else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?([^=;]*)\)?\s*=>/))) {
|
|
1450
|
+
declarations.push(nativeDeclaration(input, number, 'VariableFunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
|
|
1451
|
+
} else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b/))) {
|
|
1452
|
+
declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', 'variable', match[1], {}, false));
|
|
1453
|
+
} else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?function\*?\s*\(([^)]*)\)/))) {
|
|
1454
|
+
declarations.push(nativeDeclaration(input, number, 'CommonJsFunctionExport', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
|
|
1455
|
+
} else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=/))) {
|
|
1456
|
+
declarations.push(nativeDeclaration(input, number, 'CommonJsExport', 'variable', match[1], { export: 'commonjs' }, false));
|
|
1457
|
+
} else if (currentClass && (match = declarationLine.match(/^(?:(?:public|private|protected|static|async|override|readonly|abstract|accessor|get|set)\s+)*(?:async\s+)?(?:get\s+|set\s+)?([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*(?::\s*[^={]+)?(?:\{|=>|$)/)) && !jsControlKeyword(match[1])) {
|
|
1458
|
+
declarations.push(nativeDeclaration(input, number, 'MethodDefinition', 'method', `${currentClass}.${match[1]}`, {
|
|
1459
|
+
methodName: match[1],
|
|
1460
|
+
owner: currentClass,
|
|
1461
|
+
parameters: splitParameters(match[2])
|
|
1462
|
+
}, declarationLine.includes('{') || declarationLine.includes('=>')));
|
|
1463
|
+
} else if (currentClass && (match = declarationLine.match(/^(?:(?:public|private|protected|static|readonly|declare|accessor)\s+)*([A-Za-z_$][\w$]*)\s*(?::\s*([^=;{]+))?(?:[=;]|$)/))) {
|
|
1464
|
+
declarations.push(nativeDeclaration(input, number, 'PropertyDefinition', 'property', `${currentClass}.${match[1]}`, {
|
|
1465
|
+
propertyName: match[1],
|
|
1466
|
+
owner: currentClass,
|
|
1467
|
+
valueType: match[2]?.trim()
|
|
1468
|
+
}, false));
|
|
1469
|
+
}
|
|
1470
|
+
if (currentClass) {
|
|
1471
|
+
classDepth += braceDelta(trimmed);
|
|
1472
|
+
if (classDepth <= 0) {
|
|
1473
|
+
currentClass = undefined;
|
|
1474
|
+
classDepth = 0;
|
|
1475
|
+
}
|
|
591
1476
|
}
|
|
592
1477
|
}
|
|
593
1478
|
return declarations;
|
|
@@ -676,17 +1561,41 @@ function scanJava(input) {
|
|
|
676
1561
|
|
|
677
1562
|
function scanGo(input) {
|
|
678
1563
|
const declarations = [];
|
|
1564
|
+
let inImportBlock = false;
|
|
679
1565
|
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
680
1566
|
const trimmed = line.trim();
|
|
681
1567
|
let match;
|
|
1568
|
+
if (inImportBlock) {
|
|
1569
|
+
if (trimmed === ')') {
|
|
1570
|
+
inImportBlock = false;
|
|
1571
|
+
} else if ((match = trimmed.match(/^(?:(?:[A-Za-z_]\w*|[_.])\s+)?["']([^"']+)["']/))) {
|
|
1572
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportSpec', 'package'));
|
|
1573
|
+
}
|
|
1574
|
+
continue;
|
|
1575
|
+
}
|
|
682
1576
|
if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*)/))) {
|
|
683
1577
|
declarations.push(nativeDeclaration(input, number, 'PackageClause', 'package', match[1], {}, false));
|
|
684
|
-
} else if (
|
|
1578
|
+
} else if (/^import\s*\(/.test(trimmed)) {
|
|
1579
|
+
inImportBlock = true;
|
|
1580
|
+
} else if ((match = trimmed.match(/^import\s+(?:(?:[A-Za-z_]\w*|[_.])\s+)?["']([^"']+)["']/))) {
|
|
685
1581
|
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportSpec', 'package'));
|
|
1582
|
+
} else if ((match = trimmed.match(/^type\s+([A-Za-z_]\w*)\s*=\s*(.+)$/))) {
|
|
1583
|
+
declarations.push(nativeDeclaration(input, number, 'TypeAlias', 'type', match[1], { target: match[2].trim() }, false));
|
|
686
1584
|
} else if ((match = trimmed.match(/^type\s+([A-Za-z_]\w*)\s+(struct|interface)\b/))) {
|
|
687
1585
|
declarations.push(nativeDeclaration(input, number, match[2] === 'struct' ? 'TypeSpecStruct' : 'TypeSpecInterface', 'type', match[1], {}, trimmed.includes('{')));
|
|
688
|
-
} else if ((match = trimmed.match(/^func\s
|
|
689
|
-
|
|
1586
|
+
} else if ((match = trimmed.match(/^func\s+\(([^)]*)\)\s*([A-Za-z_]\w*)(?:\s*\[([^\]]+)\])?\s*\(([^)]*)\)/))) {
|
|
1587
|
+
const receiver = parseGoReceiver(match[1]);
|
|
1588
|
+
declarations.push(nativeDeclaration(input, number, 'MethodDecl', 'method', goReceiverMethodName(receiver, match[2]), {
|
|
1589
|
+
methodName: match[2],
|
|
1590
|
+
receiver,
|
|
1591
|
+
typeParameters: splitTypeParameters(match[3]),
|
|
1592
|
+
parameters: splitParameters(match[4])
|
|
1593
|
+
}, trimmed.includes('{')));
|
|
1594
|
+
} else if ((match = trimmed.match(/^func\s+([A-Za-z_]\w*)(?:\s*\[([^\]]+)\])?\s*\(([^)]*)\)/))) {
|
|
1595
|
+
declarations.push(nativeDeclaration(input, number, 'FuncDecl', 'function', match[1], {
|
|
1596
|
+
typeParameters: splitTypeParameters(match[2]),
|
|
1597
|
+
parameters: splitParameters(match[3])
|
|
1598
|
+
}, trimmed.includes('{')));
|
|
690
1599
|
} else if ((match = trimmed.match(/^var\s+([A-Za-z_]\w*)\b/))) {
|
|
691
1600
|
declarations.push(nativeDeclaration(input, number, 'VarDecl', 'variable', match[1], {}, false));
|
|
692
1601
|
} else if ((match = trimmed.match(/^const\s+([A-Za-z_]\w*)\b/))) {
|
|
@@ -698,17 +1607,35 @@ function scanGo(input) {
|
|
|
698
1607
|
|
|
699
1608
|
function scanSwift(input) {
|
|
700
1609
|
const declarations = [];
|
|
1610
|
+
const protocols = new Set();
|
|
701
1611
|
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
702
1612
|
const trimmed = line.trim();
|
|
1613
|
+
const declarationLine = trimmed.replace(/^(?:@[A-Za-z_][\w.]+(?:\([^)]*\))?\s+)*/, '');
|
|
703
1614
|
let match;
|
|
704
|
-
if ((match =
|
|
1615
|
+
if ((match = declarationLine.match(/^import\s+(?:(?:struct|class|enum|protocol|func|var)\s+)?([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/))) {
|
|
705
1616
|
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDecl', 'module'));
|
|
706
|
-
} else if ((match =
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1617
|
+
} else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open|final|indirect)\s+)*(struct|class|enum|protocol|actor)\s+([A-Za-z_]\w*)/))) {
|
|
1618
|
+
if (match[1] === 'protocol') protocols.add(match[2]);
|
|
1619
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Decl`, swiftSymbolKind(match[1]), match[2], {}, declarationLine.includes('{')));
|
|
1620
|
+
} else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open)\s+)*extension\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)(.*)$/))) {
|
|
1621
|
+
const extensionFields = parseSwiftExtensionTail(match[2]);
|
|
1622
|
+
const isProtocolExtension = protocols.has(match[1]) || /Protocol$/.test(match[1]);
|
|
1623
|
+
declarations.push(nativeDeclaration(input, number, isProtocolExtension ? 'ProtocolExtensionDecl' : 'ExtensionDecl', 'implementation', `${match[1]}.${isProtocolExtension ? 'protocolExtension' : 'extension'}`, {
|
|
1624
|
+
extendedType: match[1],
|
|
1625
|
+
...extensionFields
|
|
1626
|
+
}, declarationLine.includes('{')));
|
|
1627
|
+
} else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open|static|class|mutating|nonmutating|override|required|convenience|isolated|nonisolated)\s+)*func\s+([A-Za-z_]\w*|`[^`]+`)(?:\s*<([^>]+)>)?\s*\(([^)]*)\)/))) {
|
|
1628
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDecl', 'function', unquoteSwiftIdentifier(match[1]), {
|
|
1629
|
+
typeParameters: splitTypeParameters(match[2]),
|
|
1630
|
+
parameters: splitParameters(match[3])
|
|
1631
|
+
}, declarationLine.includes('{')));
|
|
1632
|
+
} else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open|static|class|final|lazy|weak|unowned|override|required|nonisolated)\s+)*(let|var)\s+([A-Za-z_]\w*)\b(?::\s*([^={]+))?/))) {
|
|
1633
|
+
declarations.push(nativeDeclaration(input, number, 'PropertyDecl', 'property', match[2], {
|
|
1634
|
+
binding: match[1],
|
|
1635
|
+
valueType: match[3]?.trim()
|
|
1636
|
+
}, declarationLine.includes('{') || declarationLine.includes('=>')));
|
|
1637
|
+
} else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open)\s+)*typealias\s+([A-Za-z_]\w*)\b(?:\s*=\s*(.+))?/))) {
|
|
1638
|
+
declarations.push(nativeDeclaration(input, number, 'TypealiasDecl', 'type', match[1], { target: match[2]?.trim() }, false));
|
|
712
1639
|
}
|
|
713
1640
|
}
|
|
714
1641
|
return declarations;
|
|
@@ -719,14 +1646,36 @@ function scanCSharp(input) {
|
|
|
719
1646
|
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
720
1647
|
const trimmed = line.trim();
|
|
721
1648
|
let match;
|
|
722
|
-
if ((match = trimmed.match(/^using\s+(
|
|
1649
|
+
if ((match = trimmed.match(/^using\s+([A-Za-z_]\w*)\s*=\s*(.+?)\s*;/))) {
|
|
1650
|
+
declarations.push(nativeDeclaration(input, number, 'UsingAliasDirective', 'type', match[1], { target: match[2].trim() }, false));
|
|
1651
|
+
} else if ((match = trimmed.match(/^using\s+(?:static\s+)?([A-Za-z_][\w.]*)\s*;/))) {
|
|
723
1652
|
declarations.push(nativeImportDeclaration(input, number, match[1], 'UsingDirective', 'namespace'));
|
|
724
1653
|
} else if ((match = trimmed.match(/^namespace\s+([A-Za-z_][\w.]*)/))) {
|
|
725
1654
|
declarations.push(nativeDeclaration(input, number, 'NamespaceDeclaration', 'namespace', match[1], {}, trimmed.includes('{')));
|
|
726
|
-
} else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|
|
|
727
|
-
declarations.push(nativeDeclaration(input, number,
|
|
728
|
-
|
|
729
|
-
|
|
1655
|
+
} else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|static|unsafe|new)\s+)*delegate\s+(.+?)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*;/))) {
|
|
1656
|
+
declarations.push(nativeDeclaration(input, number, 'DelegateDeclaration', 'type', match[2], {
|
|
1657
|
+
returnType: match[1].trim(),
|
|
1658
|
+
parameters: splitParameters(match[3])
|
|
1659
|
+
}, false));
|
|
1660
|
+
} else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|abstract|sealed|static|partial|readonly|ref|unsafe)\s+)*(class|interface|struct|enum|record(?:\s+(?:class|struct))?)\s+([A-Za-z_]\w*)/))) {
|
|
1661
|
+
declarations.push(nativeDeclaration(input, number, csharpDeclarationKind(match[1]), csharpSymbolKind(match[1]), match[2], { csharpKind: match[1].replace(/\s+/g, ' ') }, trimmed.includes('{')));
|
|
1662
|
+
} else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|static|virtual|override|async|partial|sealed|abstract|extern|new|unsafe|readonly)\s+)*(?:[A-Za-z_][\w<>\[\].?,\s]*\??|void)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*(?:=>.*|\{|;)?$/))) {
|
|
1663
|
+
const parameters = splitParameters(match[2]);
|
|
1664
|
+
const extensionReceiver = csharpExtensionReceiver(parameters);
|
|
1665
|
+
declarations.push(nativeDeclaration(input, number, extensionReceiver ? 'ExtensionMethodDeclaration' : 'MethodDeclaration', 'method', match[1], {
|
|
1666
|
+
parameters,
|
|
1667
|
+
...(extensionReceiver ? { extensionReceiver } : {})
|
|
1668
|
+
}, trimmed.includes('{') || trimmed.includes('=>')));
|
|
1669
|
+
} else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|static|virtual|override|abstract|sealed|new|unsafe)\s+)*event\s+(.+?)\s+([A-Za-z_]\w*)\s*(?:[;{=]|=>)/))) {
|
|
1670
|
+
declarations.push(nativeDeclaration(input, number, 'EventDeclaration', 'event', match[2], {
|
|
1671
|
+
eventType: match[1].trim(),
|
|
1672
|
+
accessors: csharpAccessors(trimmed)
|
|
1673
|
+
}, trimmed.includes('{')));
|
|
1674
|
+
} else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|static|virtual|override|abstract|sealed|new|required|readonly|unsafe)\s+)*([A-Za-z_][\w<>\[\].?,\s]*\??)\s+([A-Za-z_]\w*)\s*(?:\{|=>)/))) {
|
|
1675
|
+
declarations.push(nativeDeclaration(input, number, 'PropertyDeclaration', 'property', match[2], {
|
|
1676
|
+
propertyType: match[1].trim(),
|
|
1677
|
+
accessors: csharpAccessors(trimmed)
|
|
1678
|
+
}, trimmed.includes('{') || trimmed.includes('=>')));
|
|
730
1679
|
}
|
|
731
1680
|
}
|
|
732
1681
|
return declarations;
|
|
@@ -768,35 +1717,387 @@ function scanRuby(input) {
|
|
|
768
1717
|
return declarations;
|
|
769
1718
|
}
|
|
770
1719
|
|
|
771
|
-
function
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1720
|
+
function scanKotlin(input) {
|
|
1721
|
+
const declarations = [];
|
|
1722
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1723
|
+
const trimmed = line.trim();
|
|
1724
|
+
let match;
|
|
1725
|
+
if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/))) {
|
|
1726
|
+
declarations.push(nativeDeclaration(input, number, 'PackageHeader', 'package', match[1], {}, false));
|
|
1727
|
+
} else if ((match = trimmed.match(/^import\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*(?:\.\*)?)(?:\s+as\s+[A-Za-z_]\w*)?$/))) {
|
|
1728
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDirective', 'package'));
|
|
1729
|
+
} 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*)/))) {
|
|
1730
|
+
declarations.push(nativeDeclaration(input, number, kotlinDeclarationKind(match[2], match[1]), kotlinSymbolKind(match[2], match[1]), match[3], {}, trimmed.includes('{')));
|
|
1731
|
+
} 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*\(([^)]*)\)/))) {
|
|
1732
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=')));
|
|
1733
|
+
} else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual)\s+)*typealias\s+([A-Za-z_]\w*)\s*=/))) {
|
|
1734
|
+
declarations.push(nativeDeclaration(input, number, 'TypeAliasDeclaration', 'type', match[1], {}, false));
|
|
1735
|
+
} 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/))) {
|
|
1736
|
+
declarations.push(nativeDeclaration(input, number, 'PropertyDeclaration', 'variable', match[1], {}, false));
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
return declarations;
|
|
775
1740
|
}
|
|
776
1741
|
|
|
777
|
-
function
|
|
778
|
-
|
|
1742
|
+
function scanScala(input) {
|
|
1743
|
+
const declarations = [];
|
|
1744
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1745
|
+
const trimmed = line.trim();
|
|
1746
|
+
let match;
|
|
1747
|
+
if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/))) {
|
|
1748
|
+
declarations.push(nativeDeclaration(input, number, 'PackageClause', 'package', match[1], {}, false));
|
|
1749
|
+
} else if ((match = trimmed.match(/^import\s+(.+?);?$/))) {
|
|
1750
|
+
declarations.push(nativeImportDeclaration(input, number, match[1].trim(), 'Import', 'package'));
|
|
1751
|
+
} 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*)/))) {
|
|
1752
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Def`, scalaSymbolKind(match[1]), match[2], {}, trimmed.includes('{') || trimmed.includes(':')));
|
|
1753
|
+
} else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|override|inline)\s+)*def\s+([A-Za-z_]\w*)\s*(?:\[[^\]]+\])?\s*\(([^)]*)\)/))) {
|
|
1754
|
+
declarations.push(nativeDeclaration(input, number, 'DefDef', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=')));
|
|
1755
|
+
} else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|opaque)\s+)*type\s+([A-Za-z_]\w*)\b/))) {
|
|
1756
|
+
declarations.push(nativeDeclaration(input, number, 'TypeDef', 'type', match[1], {}, false));
|
|
1757
|
+
} else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|lazy|override|inline)\s+)*(?:val|var)\s+([A-Za-z_]\w*)\b/))) {
|
|
1758
|
+
declarations.push(nativeDeclaration(input, number, 'ValDef', 'variable', match[1], {}, false));
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
return declarations;
|
|
779
1762
|
}
|
|
780
1763
|
|
|
781
|
-
function
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1764
|
+
function scanDart(input) {
|
|
1765
|
+
const declarations = [];
|
|
1766
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1767
|
+
const trimmed = line.trim();
|
|
1768
|
+
let match;
|
|
1769
|
+
if ((match = trimmed.match(/^(?:import|export)\s+['"]([^'"]+)['"]/))) {
|
|
1770
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'UriBasedDirective', 'library'));
|
|
1771
|
+
} else if ((match = trimmed.match(/^part\s+['"]([^'"]+)['"]/))) {
|
|
1772
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'PartDirective', 'library'));
|
|
1773
|
+
} else if ((match = trimmed.match(/^(?:(?:abstract|base|final|interface|sealed)\s+)*(class|mixin|enum)\s+([A-Za-z_]\w*)/))) {
|
|
1774
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, dartSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
|
|
1775
|
+
} else if ((match = trimmed.match(/^extension\s+([A-Za-z_]\w*)\s+on\s+.+\{/))) {
|
|
1776
|
+
declarations.push(nativeDeclaration(input, number, 'ExtensionDeclaration', 'implementation', match[1], {}, true));
|
|
1777
|
+
} else if ((match = trimmed.match(/^typedef\s+([A-Za-z_]\w*)\b/))) {
|
|
1778
|
+
declarations.push(nativeDeclaration(input, number, 'TypeAlias', 'type', match[1], {}, false));
|
|
1779
|
+
} else if ((match = trimmed.match(/^(?:(?:external|static)\s+)*(?:[A-Za-z_]\w*(?:<[^>]+>)?\??|void)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*(?:async\s*)?(?:\{|=>|;)/))) {
|
|
1780
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=>')));
|
|
1781
|
+
} else if ((match = trimmed.match(/^(?:(?:static|external|late)\s+)*(?:const|final|var)\s+(?:[A-Za-z_]\w*(?:<[^>]+>)?\??\s+)?([A-Za-z_]\w*)\b/))) {
|
|
1782
|
+
declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', 'variable', match[1], {}, false));
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
return declarations;
|
|
785
1786
|
}
|
|
786
1787
|
|
|
787
|
-
function
|
|
788
|
-
|
|
1788
|
+
function scanLua(input) {
|
|
1789
|
+
const declarations = [];
|
|
1790
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1791
|
+
const trimmed = line.trim();
|
|
1792
|
+
let match;
|
|
1793
|
+
if ((match = trimmed.match(/^(?:local\s+[A-Za-z_]\w*\s*=\s*)?require\s*\(?\s*['"]([^'"]+)['"]\s*\)?/))) {
|
|
1794
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'RequireCall', 'module'));
|
|
1795
|
+
} else if ((match = trimmed.match(/^(?:local\s+)?function\s+([A-Za-z_]\w*(?:[.:][A-Za-z_]\w*)*)\s*\(([^)]*)\)/))) {
|
|
1796
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
|
|
1797
|
+
} else if ((match = trimmed.match(/^(?:local\s+)?([A-Za-z_]\w*(?:[.:][A-Za-z_]\w*)*)\s*=\s*function\s*\(([^)]*)\)/))) {
|
|
1798
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionAssignment', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
|
|
1799
|
+
} else if ((match = trimmed.match(/^local\s+([A-Za-z_]\w*)\s*=\s*(?:\{|\w+)/))) {
|
|
1800
|
+
declarations.push(nativeDeclaration(input, number, 'LocalDeclaration', 'variable', match[1], {}, false));
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return declarations;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
function scanShell(input) {
|
|
1807
|
+
const declarations = [];
|
|
1808
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1809
|
+
const trimmed = line.trim();
|
|
1810
|
+
let match;
|
|
1811
|
+
if ((match = trimmed.match(/^(?:source|\.)\s+(?:"([^"]+)"|'([^']+)'|([./A-Za-z0-9_-][\w./-]*))(?:\s|$)/))) {
|
|
1812
|
+
declarations.push(nativeImportDeclaration(input, number, match[1] ?? match[2] ?? match[3], 'SourceCommand', 'file'));
|
|
1813
|
+
} else if ((match = trimmed.match(/^function\s+([A-Za-z_][\w-]*)\s*(?:\(\s*\))?\s*(?:\{|$)/))) {
|
|
1814
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], {}, true));
|
|
1815
|
+
} else if ((match = trimmed.match(/^([A-Za-z_][\w-]*)\s*\(\s*\)\s*(?:\{|$)/))) {
|
|
1816
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], {}, true));
|
|
1817
|
+
} else if ((match = trimmed.match(/^(?:export\s+)?(?:readonly\s+)?([A-Za-z_]\w*)=/))) {
|
|
1818
|
+
declarations.push(nativeDeclaration(input, number, 'VariableAssignment', 'variable', match[1], {}, false));
|
|
1819
|
+
} else if ((match = trimmed.match(/^alias\s+([A-Za-z_][\w-]*)=/))) {
|
|
1820
|
+
declarations.push(nativeDeclaration(input, number, 'AliasDeclaration', 'function', match[1], {}, false));
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
return declarations;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
function scanSql(input) {
|
|
1827
|
+
const declarations = [];
|
|
1828
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1829
|
+
const trimmed = line.trim();
|
|
1830
|
+
let match;
|
|
1831
|
+
if ((match = trimmed.match(/^CREATE\s+EXTENSION\s+(?:IF\s+NOT\s+EXISTS\s+)?((?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$-]*))/i))) {
|
|
1832
|
+
declarations.push(nativeImportDeclaration(input, number, normalizeSqlIdentifier(match[1]), 'CreateExtensionStatement', 'extension'));
|
|
1833
|
+
} 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))) {
|
|
1834
|
+
const objectKind = match[1].toUpperCase().replace(/\s+/g, ' ');
|
|
1835
|
+
declarations.push(nativeDeclaration(input, number, sqlLanguageKind(objectKind), sqlSymbolKind(objectKind), normalizeSqlIdentifier(match[2]), { objectKind }, trimmed.includes('(')));
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
return declarations;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
function scanZig(input) {
|
|
1842
|
+
const declarations = [];
|
|
1843
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1844
|
+
const trimmed = line.trim();
|
|
1845
|
+
let match;
|
|
1846
|
+
if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?(?:const|var)\s+([A-Za-z_]\w*)\s*=\s*@import\(\s*["']([^"']+)["']\s*\)\s*;?/))) {
|
|
1847
|
+
declarations.push(nativeImportDeclaration(input, number, match[2], 'ImportDeclaration', 'module'));
|
|
1848
|
+
} else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?usingnamespace\s+@import\(\s*["']([^"']+)["']\s*\)\s*;?/))) {
|
|
1849
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'UsingNamespaceImport', 'module'));
|
|
1850
|
+
} else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?const\s+([A-Za-z_]\w*)\s*=\s*(?:extern\s+)?(struct|enum|union|opaque|error)\b/))) {
|
|
1851
|
+
declarations.push(nativeDeclaration(input, number, `Const${upperFirst(match[2])}Declaration`, 'type', match[1], { zigKind: match[2] }, trimmed.includes('{')));
|
|
1852
|
+
} else if ((match = trimmed.match(/^(?:(?:pub|export|extern|inline)\s+)*(?:fn)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
|
|
1853
|
+
declarations.push(nativeDeclaration(input, number, 'FnDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
1854
|
+
} else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?const\s+([A-Za-z_]\w*)\b/))) {
|
|
1855
|
+
declarations.push(nativeDeclaration(input, number, 'ConstDeclaration', 'constant', match[1], {}, false));
|
|
1856
|
+
} else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?var\s+([A-Za-z_]\w*)\b/))) {
|
|
1857
|
+
declarations.push(nativeDeclaration(input, number, 'VarDeclaration', 'variable', match[1], {}, false));
|
|
1858
|
+
}
|
|
1859
|
+
if (/^\s*comptime\b|@(?:cImport|compileError|field|hasDecl|hasField|setEvalBranchQuota|This|Type|typeInfo)\b/.test(trimmed)) {
|
|
1860
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'generatedCode', zigMetaName(trimmed)));
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
return declarations;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
function scanElixir(input) {
|
|
1867
|
+
const declarations = [];
|
|
1868
|
+
let currentModule;
|
|
1869
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1870
|
+
const trimmed = line.trim();
|
|
1871
|
+
let match;
|
|
1872
|
+
let recordedMeta = false;
|
|
1873
|
+
if ((match = trimmed.match(/^defmodule\s+([A-Z]\w*(?:\.[A-Z]\w*)*)\s+do\b/))) {
|
|
1874
|
+
currentModule = match[1];
|
|
1875
|
+
declarations.push(nativeDeclaration(input, number, 'ModuleDefinition', 'module', match[1], {}, true));
|
|
1876
|
+
} else if ((match = trimmed.match(/^(?:alias|import|require)\s+([A-Z]\w*(?:\.[A-Z]\w*)*)/))) {
|
|
1877
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDirective', 'module'));
|
|
1878
|
+
} else if ((match = trimmed.match(/^use\s+([A-Z]\w*(?:\.[A-Z]\w*)*)/))) {
|
|
1879
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', match[1]));
|
|
1880
|
+
recordedMeta = true;
|
|
1881
|
+
} else if ((match = trimmed.match(/^(defmacro|defmacrop|defguard|defguardp|defdelegate)\s+([A-Za-z_]\w*[!?]?)/))) {
|
|
1882
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', match[2]));
|
|
1883
|
+
recordedMeta = true;
|
|
1884
|
+
} else if ((match = trimmed.match(/^defp?\s+([A-Za-z_]\w*[!?]?)\s*(?:\(([^)]*)\)|([^,]*))?/))) {
|
|
1885
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], { parameters: splitParameters(match[2] ?? match[3]) }, /\bdo\b/.test(trimmed)));
|
|
1886
|
+
} else if (trimmed.startsWith('defstruct')) {
|
|
1887
|
+
declarations.push(nativeDeclaration(input, number, 'StructDefinition', 'type', currentModule ?? `struct_${number}`, {}, true));
|
|
1888
|
+
} else if ((match = trimmed.match(/^@(type|typep|opaque|callback)\s+([A-Za-z_]\w*[!?]?)/))) {
|
|
1889
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Attribute`, match[1] === 'callback' ? 'function' : 'type', match[2], {}, false));
|
|
1890
|
+
}
|
|
1891
|
+
if (!recordedMeta && /(?:\bquote\s+do\b|\bunquote(?:_splicing)?\b|@(?:before_compile|after_compile|on_definition|derive)\b)/.test(trimmed)) {
|
|
1892
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', elixirMetaName(trimmed)));
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return declarations;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
function scanErlang(input) {
|
|
1899
|
+
const declarations = [];
|
|
1900
|
+
const seenFunctions = new Set();
|
|
1901
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1902
|
+
const trimmed = line.trim();
|
|
1903
|
+
let match;
|
|
1904
|
+
let recordedMacro = false;
|
|
1905
|
+
if ((match = trimmed.match(/^-module\(([a-z][A-Za-z0-9_@]*)\)\./))) {
|
|
1906
|
+
declarations.push(nativeDeclaration(input, number, 'ModuleAttribute', 'module', match[1], {}, false));
|
|
1907
|
+
} else if ((match = trimmed.match(/^-include(?:_lib)?\(["']([^"']+)["']\)\./))) {
|
|
1908
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'IncludeAttribute', 'module'));
|
|
1909
|
+
} else if ((match = trimmed.match(/^-import\(([a-z][A-Za-z0-9_@]*)\s*,/))) {
|
|
1910
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportAttribute', 'module'));
|
|
1911
|
+
} else if ((match = trimmed.match(/^-behaviou?r\(([a-z][A-Za-z0-9_@]*)\)\./))) {
|
|
1912
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'BehaviourAttribute', 'module'));
|
|
1913
|
+
} else if ((match = trimmed.match(/^-record\(([a-z][A-Za-z0-9_@]*)\s*,/))) {
|
|
1914
|
+
declarations.push(nativeDeclaration(input, number, 'RecordAttribute', 'type', match[1], {}, false));
|
|
1915
|
+
} else if ((match = trimmed.match(/^-(type|opaque)\s+([a-z][A-Za-z0-9_@]*)\s*\(/))) {
|
|
1916
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Attribute`, 'type', match[2], {}, false));
|
|
1917
|
+
} else if ((match = trimmed.match(/^-callback\s+([a-z][A-Za-z0-9_@]*)\s*\(/))) {
|
|
1918
|
+
declarations.push(nativeDeclaration(input, number, 'CallbackAttribute', 'function', match[1], {}, false));
|
|
1919
|
+
} else if ((match = trimmed.match(/^-define\(([^,\s)]+)/))) {
|
|
1920
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'preprocessor', match[1]));
|
|
1921
|
+
recordedMacro = true;
|
|
1922
|
+
} else if (/^-compile\([^)]*parse_transform/.test(trimmed)) {
|
|
1923
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'generatedCode', 'parse_transform'));
|
|
1924
|
+
recordedMacro = true;
|
|
1925
|
+
} else if ((match = trimmed.match(/^([a-z][A-Za-z0-9_@]*|'[^']+')\s*\(([^)]*)\)\s*(?:when\s+.*?)?->/))) {
|
|
1926
|
+
const name = erlangAtomName(match[1]);
|
|
1927
|
+
if (!seenFunctions.has(name)) {
|
|
1928
|
+
seenFunctions.add(name);
|
|
1929
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionClause', 'function', name, { parameters: splitParameters(match[2]) }, true));
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
if (!recordedMacro && /(^|[^A-Za-z0-9_])\?[A-Za-z_]\w*/.test(trimmed)) {
|
|
1933
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', erlangMacroName(trimmed)));
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
return declarations;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
function scanHaskell(input) {
|
|
1940
|
+
const declarations = [];
|
|
1941
|
+
const seenFunctions = new Set();
|
|
1942
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1943
|
+
const trimmed = line.trim();
|
|
1944
|
+
let match;
|
|
1945
|
+
if (/^#\s*(?:if|ifdef|ifndef|else|elif|endif|define|include)\b/.test(trimmed)) {
|
|
1946
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'preprocessor', haskellMetaName(trimmed)));
|
|
1947
|
+
} else if ((match = trimmed.match(/^\{-#\s+LANGUAGE\s+(.+?)#-\}/))) {
|
|
1948
|
+
const extensions = match[1].split(',').map((part) => part.trim());
|
|
1949
|
+
if (extensions.some((extension) => /^(TemplateHaskell|QuasiQuotes|CPP)$/.test(extension))) {
|
|
1950
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, extensions.includes('CPP') ? 'preprocessor' : 'macroExpansion', extensions.join('_')));
|
|
1951
|
+
}
|
|
1952
|
+
} else if (/^\$\(|\[[a-zA-Z]*\||\b\$\([^)]+\)/.test(trimmed)) {
|
|
1953
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', haskellMetaName(trimmed)));
|
|
1954
|
+
} else if ((match = trimmed.match(/^module\s+([A-Z][A-Za-z0-9_.']*)\b/))) {
|
|
1955
|
+
declarations.push(nativeDeclaration(input, number, 'ModuleDeclaration', 'module', match[1], {}, false));
|
|
1956
|
+
} else if ((match = trimmed.match(/^import\s+(?:safe\s+)?(?:qualified\s+)?([A-Z][A-Za-z0-9_.']*)/))) {
|
|
1957
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'module'));
|
|
1958
|
+
} else if ((match = trimmed.match(/^foreign\s+import\s+([A-Za-z_]\w*)/))) {
|
|
1959
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ForeignImportDeclaration', 'foreign'));
|
|
1960
|
+
} else if ((match = trimmed.match(/^(data|newtype|type)\s+([A-Z][A-Za-z0-9_']*)\b/))) {
|
|
1961
|
+
declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, 'type', match[2], {}, /(?:where|=)/.test(trimmed)));
|
|
1962
|
+
} else if ((match = trimmed.match(/^class\s+(?:\([^)]*\)\s*=>\s*)?([A-Z][A-Za-z0-9_']*)\b/))) {
|
|
1963
|
+
declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'type', match[1], {}, /\bwhere\b/.test(trimmed)));
|
|
1964
|
+
} else if ((match = trimmed.match(/^([a-z_][A-Za-z0-9_']*)\s*::\s*(.+)$/))) {
|
|
1965
|
+
seenFunctions.add(match[1]);
|
|
1966
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionSignature', 'function', match[1], { signature: match[2].trim() }, false));
|
|
1967
|
+
} else if ((match = trimmed.match(/^([a-z_][A-Za-z0-9_']*)\b[^=]*=/))) {
|
|
1968
|
+
if (!seenFunctions.has(match[1])) {
|
|
1969
|
+
seenFunctions.add(match[1]);
|
|
1970
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionBinding', 'function', match[1], {}, true));
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
return declarations;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
function scanR(input) {
|
|
1978
|
+
const declarations = [];
|
|
1979
|
+
for (const { line, number } of sourceLines(input.sourceText)) {
|
|
1980
|
+
const trimmed = line.trim();
|
|
1981
|
+
let match;
|
|
1982
|
+
if ((match = trimmed.match(/^(?:library|require)\s*\(\s*["']?([A-Za-z_][\w.-]*)["']?/))) {
|
|
1983
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'LibraryCall', 'package'));
|
|
1984
|
+
} else if ((match = trimmed.match(/^importFrom\s*\(\s*["']?([A-Za-z_][\w.-]*)["']?/))) {
|
|
1985
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportFromCall', 'package'));
|
|
1986
|
+
} else if ((match = trimmed.match(/^source\s*\(\s*["']([^"']+)["']/))) {
|
|
1987
|
+
declarations.push(nativeImportDeclaration(input, number, match[1], 'SourceCall', 'module'));
|
|
1988
|
+
} else if ((match = trimmed.match(/^([A-Za-z_][\w.]*)\s*(?:<-|=)\s*function\s*\(([^)]*)\)/))) {
|
|
1989
|
+
declarations.push(nativeDeclaration(input, number, 'FunctionAssignment', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
|
|
1990
|
+
} else if ((match = trimmed.match(/^([A-Za-z_][\w.]*)\s*<-\s*R6Class\s*\(\s*["']([^"']+)["']/))) {
|
|
1991
|
+
declarations.push(nativeDeclaration(input, number, 'R6ClassDeclaration', 'class', match[2] || match[1], { binding: match[1] }, true));
|
|
1992
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[2] || match[1]));
|
|
1993
|
+
} else if ((match = trimmed.match(/^setClass\s*\(\s*["']([^"']+)["']/))) {
|
|
1994
|
+
declarations.push(nativeDeclaration(input, number, 'S4ClassDeclaration', 'class', match[1], {}, true));
|
|
1995
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[1]));
|
|
1996
|
+
} else if ((match = trimmed.match(/^setGeneric\s*\(\s*["']([^"']+)["']/))) {
|
|
1997
|
+
declarations.push(nativeDeclaration(input, number, 'S4GenericDeclaration', 'function', match[1], {}, true));
|
|
1998
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[1]));
|
|
1999
|
+
} else if ((match = trimmed.match(/^setMethod\s*\(\s*["']([^"']+)["']/))) {
|
|
2000
|
+
declarations.push(nativeDeclaration(input, number, 'S4MethodDeclaration', 'method', match[1], {}, true));
|
|
2001
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicDispatch', match[1]));
|
|
2002
|
+
} else if ((match = trimmed.match(/^([A-Z][A-Za-z0-9_.]*)\s*(?:<-|=)\s*/))) {
|
|
2003
|
+
declarations.push(nativeDeclaration(input, number, 'ConstantAssignment', 'constant', match[1], {}, false));
|
|
2004
|
+
}
|
|
2005
|
+
if (/(?:eval|parse|substitute|quote|bquote|assign)\s*\(|<<-|\{\{|!!!|!!/.test(trimmed)) {
|
|
2006
|
+
declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', rMetaName(trimmed)));
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
return declarations;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
function scanGenericDeclarations(input) {
|
|
2013
|
+
return sourceLines(input.sourceText)
|
|
2014
|
+
.filter(({ line }) => /\b(function|class|struct|enum|trait|interface|def)\b/.test(line))
|
|
2015
|
+
.map(({ line, number }) => nativeDeclaration(input, number, 'NativeDeclaration', 'variable', idFragment(line.trim()).slice(0, 40), { source: line.trim() }, true));
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
function upperFirst(value) {
|
|
2019
|
+
return String(value).charAt(0).toUpperCase() + String(value).slice(1);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
function parseGoReceiver(raw) {
|
|
2023
|
+
const value = String(raw ?? '').trim();
|
|
2024
|
+
const match = value.match(/^(?:(\w+)\s+)?(.+)$/);
|
|
2025
|
+
const rawType = String(match?.[2] ?? value).trim();
|
|
2026
|
+
return {
|
|
2027
|
+
raw: value,
|
|
2028
|
+
...(match?.[1] ? { name: match[1] } : {}),
|
|
2029
|
+
rawType,
|
|
2030
|
+
type: normalizeGoReceiverType(rawType)
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
function normalizeGoReceiverType(rawType) {
|
|
2035
|
+
return String(rawType ?? '')
|
|
2036
|
+
.trim()
|
|
2037
|
+
.replace(/^[*&\s]+/, '')
|
|
2038
|
+
.replace(/\[[^\]]+\]/g, '')
|
|
2039
|
+
.replace(/\s+/g, ' ');
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
function goReceiverMethodName(receiver, methodName) {
|
|
2043
|
+
return receiver?.type ? `${receiver.type}.${methodName}` : methodName;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
function parseSwiftExtensionTail(rawTail) {
|
|
2047
|
+
let tail = String(rawTail ?? '').split('{')[0].trim();
|
|
2048
|
+
const fields = {};
|
|
2049
|
+
const whereMatch = tail.match(/\bwhere\b(.+)$/);
|
|
2050
|
+
if (whereMatch) {
|
|
2051
|
+
fields.constraints = whereMatch[1].trim();
|
|
2052
|
+
tail = tail.slice(0, whereMatch.index).trim();
|
|
2053
|
+
}
|
|
2054
|
+
if (tail.startsWith(':')) {
|
|
2055
|
+
fields.conformances = tail.slice(1).split(',').map((part) => part.trim()).filter(Boolean);
|
|
2056
|
+
}
|
|
2057
|
+
return fields;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
function unquoteSwiftIdentifier(identifier) {
|
|
2061
|
+
return String(identifier).replace(/^`|`$/g, '');
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
function javaSymbolKind(kind) {
|
|
2065
|
+
if (kind === 'interface' || kind === '@interface') return 'interface';
|
|
2066
|
+
if (kind === 'enum' || kind === 'record') return 'type';
|
|
2067
|
+
return 'class';
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
function swiftSymbolKind(kind) {
|
|
2071
|
+
if (kind === 'protocol') return 'protocol';
|
|
789
2072
|
if (kind === 'extension') return 'implementation';
|
|
790
2073
|
if (kind === 'struct' || kind === 'enum' || kind === 'actor') return 'type';
|
|
791
2074
|
return 'class';
|
|
792
2075
|
}
|
|
793
2076
|
|
|
794
2077
|
function csharpSymbolKind(kind) {
|
|
795
|
-
|
|
796
|
-
if (
|
|
2078
|
+
const normalized = String(kind).replace(/\s+/g, ' ');
|
|
2079
|
+
if (normalized === 'interface') return 'interface';
|
|
2080
|
+
if (normalized === 'struct' || normalized === 'enum' || normalized.startsWith('record')) return 'type';
|
|
797
2081
|
return 'class';
|
|
798
2082
|
}
|
|
799
2083
|
|
|
2084
|
+
function csharpDeclarationKind(kind) {
|
|
2085
|
+
const normalized = String(kind).replace(/\s+/g, ' ');
|
|
2086
|
+
if (normalized === 'record struct') return 'RecordStructDeclaration';
|
|
2087
|
+
if (normalized === 'record class') return 'RecordClassDeclaration';
|
|
2088
|
+
if (normalized === 'record') return 'RecordDeclaration';
|
|
2089
|
+
return `${upperFirst(normalized)}Declaration`;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
function csharpExtensionReceiver(parameters) {
|
|
2093
|
+
const match = String(parameters?.[0] ?? '').match(/^this\s+(.+?)\s+([A-Za-z_]\w*)$/);
|
|
2094
|
+
return match ? { type: match[1].trim(), name: match[2] } : undefined;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
function csharpAccessors(source) {
|
|
2098
|
+
return uniqueStrings([...String(source ?? '').matchAll(/\b(get|set|init|add|remove)\b/g)].map((match) => match[1]));
|
|
2099
|
+
}
|
|
2100
|
+
|
|
800
2101
|
function phpSymbolKind(kind) {
|
|
801
2102
|
if (kind === 'interface') return 'interface';
|
|
802
2103
|
if (kind === 'trait') return 'trait';
|
|
@@ -804,6 +2105,78 @@ function phpSymbolKind(kind) {
|
|
|
804
2105
|
return 'class';
|
|
805
2106
|
}
|
|
806
2107
|
|
|
2108
|
+
function kotlinDeclarationKind(kind, prefix) {
|
|
2109
|
+
if (prefix === 'enum') return 'EnumClassDeclaration';
|
|
2110
|
+
if (prefix === 'annotation') return 'AnnotationClassDeclaration';
|
|
2111
|
+
return `${upperFirst(kind)}Declaration`;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
function kotlinSymbolKind(kind, prefix) {
|
|
2115
|
+
if (kind === 'interface') return 'interface';
|
|
2116
|
+
if (kind === 'object') return 'module';
|
|
2117
|
+
if (prefix === 'enum' || prefix === 'annotation') return 'type';
|
|
2118
|
+
return 'class';
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
function scalaSymbolKind(kind) {
|
|
2122
|
+
if (kind === 'trait') return 'trait';
|
|
2123
|
+
if (kind === 'object') return 'module';
|
|
2124
|
+
if (kind === 'enum') return 'type';
|
|
2125
|
+
return 'class';
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
function dartSymbolKind(kind) {
|
|
2129
|
+
if (kind === 'mixin') return 'trait';
|
|
2130
|
+
if (kind === 'enum') return 'type';
|
|
2131
|
+
return 'class';
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
function sqlSymbolKind(kind) {
|
|
2135
|
+
if (kind === 'FUNCTION' || kind === 'PROCEDURE' || kind === 'TRIGGER') return 'function';
|
|
2136
|
+
if (kind.includes('INDEX')) return 'index';
|
|
2137
|
+
if (kind === 'SCHEMA') return 'namespace';
|
|
2138
|
+
return 'type';
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
function sqlLanguageKind(kind) {
|
|
2142
|
+
return `Create${kind.toLowerCase().split(/\s+/).map(upperFirst).join('')}Statement`;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
function normalizeSqlIdentifier(value) {
|
|
2146
|
+
return String(value)
|
|
2147
|
+
.split(/\s*\.\s*/)
|
|
2148
|
+
.map((part) => part.replace(/^"|"$/g, '').replace(/^`|`$/g, '').replace(/^\[|\]$/g, ''))
|
|
2149
|
+
.join('.');
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
function zigMetaName(source) {
|
|
2153
|
+
const match = source.match(/@([A-Za-z_]\w*)|^\s*(comptime)\b/);
|
|
2154
|
+
return match?.[1] ?? match?.[2] ?? 'comptime';
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
function elixirMetaName(source) {
|
|
2158
|
+
const match = source.match(/@([A-Za-z_]\w*)|\b(unquote(?:_splicing)?|quote)\b/);
|
|
2159
|
+
return match?.[1] ?? match?.[2] ?? 'macro';
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
function erlangAtomName(value) {
|
|
2163
|
+
return String(value).startsWith("'") ? String(value).slice(1, -1) : String(value);
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
function erlangMacroName(source) {
|
|
2167
|
+
const match = source.match(/\?([A-Za-z_]\w*)/);
|
|
2168
|
+
return match?.[1] ?? 'macro';
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
function haskellMetaName(source) {
|
|
2172
|
+
return idFragment(source).slice(0, 40);
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
function rMetaName(source) {
|
|
2176
|
+
const match = source.match(/([A-Za-z_][\w.]*)\s*\(/);
|
|
2177
|
+
return match?.[1] ?? 'dynamic';
|
|
2178
|
+
}
|
|
2179
|
+
|
|
807
2180
|
function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fields = {}, hasBody = false) {
|
|
808
2181
|
const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
|
|
809
2182
|
return {
|
|
@@ -945,6 +2318,23 @@ function splitParameters(raw) {
|
|
|
945
2318
|
.filter(Boolean);
|
|
946
2319
|
}
|
|
947
2320
|
|
|
2321
|
+
function splitTypeParameters(raw) {
|
|
2322
|
+
return splitParameters(raw);
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
function braceDelta(source) {
|
|
2326
|
+
let delta = 0;
|
|
2327
|
+
for (const char of String(source ?? '')) {
|
|
2328
|
+
if (char === '{') delta += 1;
|
|
2329
|
+
if (char === '}') delta -= 1;
|
|
2330
|
+
}
|
|
2331
|
+
return delta;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
function jsControlKeyword(value) {
|
|
2335
|
+
return ['if', 'for', 'while', 'switch', 'catch', 'with'].includes(String(value));
|
|
2336
|
+
}
|
|
2337
|
+
|
|
948
2338
|
function inferSourceMapMappings(input) {
|
|
949
2339
|
const semanticIndex = input.semanticIndex;
|
|
950
2340
|
const nativeAst = input.nativeAst;
|
|
@@ -990,33 +2380,61 @@ function inferSourceMapMappings(input) {
|
|
|
990
2380
|
}
|
|
991
2381
|
|
|
992
2382
|
function normalizeSourceMapMappings(mappings, context) {
|
|
2383
|
+
if (mappings === undefined || mappings === null) return [];
|
|
2384
|
+
if (!Array.isArray(mappings)) {
|
|
2385
|
+
throw new Error('Source-map mappings must be an array');
|
|
2386
|
+
}
|
|
993
2387
|
const semanticIndex = context.semanticIndex;
|
|
994
2388
|
const nativeAst = context.nativeAst;
|
|
995
2389
|
const nativeSource = context.nativeSource;
|
|
996
2390
|
const symbolsById = new Map((semanticIndex?.symbols ?? []).map((symbol) => [symbol.id, symbol]));
|
|
997
2391
|
const occurrencesById = new Map((semanticIndex?.occurrences ?? []).map((occurrence) => [occurrence.id, occurrence]));
|
|
998
|
-
const evidenceIds = (
|
|
999
|
-
|
|
1000
|
-
.
|
|
2392
|
+
const evidenceIds = uniqueStrings([
|
|
2393
|
+
...(semanticIndex?.evidence ?? []).map((record) => record.id),
|
|
2394
|
+
...(context.evidence ?? []).map((record) => record.id),
|
|
2395
|
+
...(context.sourceMapEvidence ?? []).map((record) => record.id)
|
|
2396
|
+
]);
|
|
2397
|
+
const usedMappingIds = new Set();
|
|
2398
|
+
return mappings
|
|
1001
2399
|
.map((mapping, index) => {
|
|
2400
|
+
if (!mapping || typeof mapping !== 'object') {
|
|
2401
|
+
throw new Error(`Source-map mapping ${index + 1} must be an object`);
|
|
2402
|
+
}
|
|
1002
2403
|
const occurrence = mapping.semanticOccurrenceId ? occurrencesById.get(mapping.semanticOccurrenceId) : undefined;
|
|
1003
2404
|
const symbol = mapping.semanticSymbolId ? symbolsById.get(mapping.semanticSymbolId) : occurrence ? symbolsById.get(occurrence.symbolId) : undefined;
|
|
1004
2405
|
const nativeAstNodeId = mapping.nativeAstNodeId ?? occurrence?.nativeAstNodeId;
|
|
1005
2406
|
const nativeNode = nativeAstNodeId ? nativeAst?.nodes?.[nativeAstNodeId] : undefined;
|
|
1006
2407
|
const sourceSpan = mapping.sourceSpan ?? occurrence?.span ?? nativeNode?.span;
|
|
1007
|
-
|
|
2408
|
+
const target = mapping.target ?? mapping.generatedSpan?.target ?? context.target;
|
|
2409
|
+
const generatedSpan = normalizeGeneratedSpan(mapping.generatedSpan, target, context.targetPath, context.targetHash);
|
|
2410
|
+
if (
|
|
2411
|
+
!nativeAstNodeId &&
|
|
2412
|
+
!mapping.semanticNodeId &&
|
|
2413
|
+
!mapping.semanticSymbolId &&
|
|
2414
|
+
!mapping.semanticOccurrenceId &&
|
|
2415
|
+
!sourceSpan &&
|
|
2416
|
+
!generatedSpan &&
|
|
2417
|
+
!mapping.generatedName
|
|
2418
|
+
) {
|
|
2419
|
+
throw new Error(`Source-map mapping ${index + 1} must reference a native AST node, semantic node, symbol, occurrence, source span, or generated span`);
|
|
2420
|
+
}
|
|
2421
|
+
const normalizedMapping = {
|
|
1008
2422
|
...mapping,
|
|
1009
|
-
id: mapping.id ?? `map_${idFragment(nativeAstNodeId ?? mapping.semanticSymbolId ?? mapping.semanticNodeId ?? index + 1)}`,
|
|
1010
2423
|
nativeSourceId: mapping.nativeSourceId ?? nativeSource?.id,
|
|
1011
2424
|
nativeAstNodeId,
|
|
1012
2425
|
semanticSymbolId: mapping.semanticSymbolId ?? occurrence?.symbolId,
|
|
1013
2426
|
semanticOccurrenceId: mapping.semanticOccurrenceId ?? occurrence?.id,
|
|
1014
2427
|
semanticNodeId: mapping.semanticNodeId ?? occurrence?.semanticNodeId ?? symbol?.semanticNodeId,
|
|
1015
2428
|
sourceSpan,
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
2429
|
+
generatedSpan,
|
|
2430
|
+
target,
|
|
2431
|
+
evidenceIds: normalizeReferenceIds(mapping.evidenceIds, evidenceIds),
|
|
2432
|
+
lossIds: normalizeReferenceIds(mapping.lossIds, lossIdsForNativeNode(context.losses ?? nativeAst?.losses ?? [], nativeAstNodeId)),
|
|
2433
|
+
precision: normalizeSourceMapPrecision(mapping.precision, sourceSpan, generatedSpan)
|
|
2434
|
+
};
|
|
2435
|
+
return {
|
|
2436
|
+
...normalizedMapping,
|
|
2437
|
+
id: reserveUniqueId(sourceMapMappingBaseId(normalizedMapping, index), usedMappingIds)
|
|
1020
2438
|
};
|
|
1021
2439
|
});
|
|
1022
2440
|
}
|
|
@@ -1028,80 +2446,1152 @@ function lossIdsForNativeNode(losses, nativeAstNodeId) {
|
|
|
1028
2446
|
.map((loss) => loss.id);
|
|
1029
2447
|
}
|
|
1030
2448
|
|
|
1031
|
-
|
|
1032
|
-
return
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
2449
|
+
function normalizeSourceMaps(sourceMaps, context) {
|
|
2450
|
+
if (sourceMaps === undefined || sourceMaps === null) return [];
|
|
2451
|
+
if (!Array.isArray(sourceMaps)) {
|
|
2452
|
+
throw new Error('Native import sourceMaps must be an array');
|
|
2453
|
+
}
|
|
2454
|
+
const usedSourceMapIds = new Set();
|
|
2455
|
+
return sourceMaps.map((sourceMap, index) => {
|
|
2456
|
+
if (!sourceMap || typeof sourceMap !== 'object') {
|
|
2457
|
+
throw new Error(`Native import source map ${index + 1} must be an object`);
|
|
2458
|
+
}
|
|
2459
|
+
const id = reserveUniqueId(String(sourceMap.id ?? `${context.defaultSourceMapId}_${index + 1}`), usedSourceMapIds);
|
|
2460
|
+
const evidence = uniqueRecordsById([...(sourceMap.evidence ?? []), ...(context.evidence ?? [])]);
|
|
2461
|
+
const target = sourceMap.target ?? context.target;
|
|
2462
|
+
const targetPath = sourceMap.targetPath ?? context.targetPath;
|
|
2463
|
+
const targetHash = sourceMap.targetHash ?? context.targetHash;
|
|
2464
|
+
const mappings = normalizeSourceMapMappings(sourceMap.mappings ?? [], {
|
|
2465
|
+
...context,
|
|
2466
|
+
target,
|
|
2467
|
+
targetPath,
|
|
2468
|
+
targetHash,
|
|
2469
|
+
sourceMapEvidence: evidence
|
|
2470
|
+
});
|
|
2471
|
+
const normalized = createSourceMapRecord({
|
|
2472
|
+
...sourceMap,
|
|
2473
|
+
id,
|
|
2474
|
+
sourcePath: sourceMap.sourcePath ?? context.sourcePath,
|
|
2475
|
+
sourceHash: sourceMap.sourceHash ?? context.sourceHash,
|
|
2476
|
+
target,
|
|
2477
|
+
targetPath: targetPath ?? commonGeneratedTargetPath(mappings),
|
|
2478
|
+
targetHash,
|
|
2479
|
+
semanticIndexId: sourceMap.semanticIndexId ?? context.semanticIndex?.id,
|
|
2480
|
+
nativeAstId: sourceMap.nativeAstId ?? context.nativeAst?.id,
|
|
2481
|
+
nativeSourceId: sourceMap.nativeSourceId ?? context.nativeSource?.id,
|
|
2482
|
+
mappings,
|
|
2483
|
+
evidence
|
|
2484
|
+
});
|
|
2485
|
+
const issues = validateSourceMapRecord(normalized, {
|
|
2486
|
+
document: context.document,
|
|
2487
|
+
nativeSources: context.nativeSources,
|
|
2488
|
+
nativeAst: context.nativeAst,
|
|
2489
|
+
semanticIndex: context.semanticIndex,
|
|
2490
|
+
losses: context.losses,
|
|
2491
|
+
evidence: context.evidence
|
|
2492
|
+
});
|
|
2493
|
+
if (issues.length) {
|
|
2494
|
+
throw new Error(`Invalid Frontier native source map ${normalized.id}: ${issues.join('; ')}`);
|
|
2495
|
+
}
|
|
2496
|
+
return normalized;
|
|
1039
2497
|
});
|
|
1040
2498
|
}
|
|
1041
2499
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
2500
|
+
function normalizeGeneratedSpan(generatedSpan, target, targetPath, targetHash) {
|
|
2501
|
+
if (!generatedSpan) return generatedSpan;
|
|
2502
|
+
return {
|
|
2503
|
+
...generatedSpan,
|
|
2504
|
+
target: generatedSpan.target ?? target,
|
|
2505
|
+
targetPath: generatedSpan.targetPath ?? target?.emitPath ?? targetPath,
|
|
2506
|
+
targetHash: generatedSpan.targetHash ?? targetHash
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
function normalizeSourceMapPrecision(value, sourceSpan, generatedSpan) {
|
|
2511
|
+
const explicit = value === undefined || value === null ? '' : String(value).trim();
|
|
2512
|
+
if (explicit) {
|
|
2513
|
+
const normalized = explicit.toLowerCase();
|
|
2514
|
+
if (normalized === 'exact' || normalized === 'declaration' || normalized === 'line' || normalized === 'estimated' || normalized === 'unknown') return normalized;
|
|
2515
|
+
if (normalized === 'estimate' || normalized === 'approx' || normalized === 'approximate' || normalized === 'approximated') return 'estimated';
|
|
2516
|
+
return explicit;
|
|
1047
2517
|
}
|
|
1048
|
-
return
|
|
2518
|
+
if (hasExactSpan(sourceSpan) && hasExactSpan(generatedSpan)) return 'exact';
|
|
2519
|
+
if (sourceSpan?.startLine && generatedSpan?.startLine) return 'line';
|
|
2520
|
+
if (sourceSpan?.startLine) return 'declaration';
|
|
2521
|
+
if (generatedSpan?.startLine) return 'line';
|
|
2522
|
+
return 'unknown';
|
|
1049
2523
|
}
|
|
1050
2524
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
2525
|
+
function hasExactSpan(span) {
|
|
2526
|
+
return Boolean(span && (
|
|
2527
|
+
(typeof span.start === 'number' && typeof span.end === 'number') ||
|
|
2528
|
+
(
|
|
2529
|
+
typeof span.startLine === 'number' &&
|
|
2530
|
+
typeof span.startColumn === 'number' &&
|
|
2531
|
+
typeof span.endLine === 'number' &&
|
|
2532
|
+
typeof span.endColumn === 'number'
|
|
2533
|
+
)
|
|
2534
|
+
));
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
function normalizeReferenceIds(value, fallback = []) {
|
|
2538
|
+
if (value === undefined || value === null) return uniqueStrings(fallback);
|
|
2539
|
+
return uniqueStrings(Array.isArray(value) ? value : [value]);
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
function sourceMapMappingBaseId(mapping, index) {
|
|
2543
|
+
const explicit = mapping.id === undefined || mapping.id === null ? '' : String(mapping.id).trim();
|
|
2544
|
+
if (explicit) return explicit;
|
|
2545
|
+
const span = mapping.sourceSpan ?? mapping.generatedSpan;
|
|
2546
|
+
const spanPart = span
|
|
2547
|
+
? `${span.path ?? span.targetPath ?? span.sourceId ?? 'span'}_${span.start ?? span.startLine ?? ''}_${span.end ?? span.endLine ?? ''}`
|
|
2548
|
+
: undefined;
|
|
2549
|
+
return `map_${idFragment([
|
|
2550
|
+
mapping.nativeAstNodeId,
|
|
2551
|
+
mapping.semanticOccurrenceId,
|
|
2552
|
+
mapping.semanticSymbolId,
|
|
2553
|
+
mapping.semanticNodeId,
|
|
2554
|
+
spanPart,
|
|
2555
|
+
index + 1
|
|
2556
|
+
].filter(Boolean).join('_'))}`;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
function reserveUniqueId(baseId, usedIds) {
|
|
2560
|
+
const safeBase = String(baseId || 'id');
|
|
2561
|
+
if (!usedIds.has(safeBase)) {
|
|
2562
|
+
usedIds.add(safeBase);
|
|
2563
|
+
return safeBase;
|
|
1055
2564
|
}
|
|
1056
|
-
|
|
2565
|
+
let index = 2;
|
|
2566
|
+
while (usedIds.has(`${safeBase}_${index}`)) index += 1;
|
|
2567
|
+
const id = `${safeBase}_${index}`;
|
|
2568
|
+
usedIds.add(id);
|
|
2569
|
+
return id;
|
|
1057
2570
|
}
|
|
1058
2571
|
|
|
1059
|
-
|
|
1060
|
-
|
|
2572
|
+
function commonGeneratedTargetPath(mappings) {
|
|
2573
|
+
const paths = uniqueStrings((mappings ?? [])
|
|
2574
|
+
.map((mapping) => mapping.generatedSpan?.targetPath ?? mapping.target?.emitPath)
|
|
2575
|
+
.filter(Boolean));
|
|
2576
|
+
return paths.length === 1 ? paths[0] : undefined;
|
|
1061
2577
|
}
|
|
1062
2578
|
|
|
1063
|
-
function
|
|
1064
|
-
|
|
1065
|
-
|
|
2579
|
+
function uniqueRecordsById(records) {
|
|
2580
|
+
const seen = new Set();
|
|
2581
|
+
const result = [];
|
|
2582
|
+
for (const record of records ?? []) {
|
|
2583
|
+
if (!record?.id || seen.has(record.id)) continue;
|
|
2584
|
+
seen.add(record.id);
|
|
2585
|
+
result.push(record);
|
|
1066
2586
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
2587
|
+
return result;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
function normalizeNativeLossRecords(losses) {
|
|
2591
|
+
return (Array.isArray(losses) ? losses : [losses])
|
|
2592
|
+
.filter((loss) => loss !== undefined && loss !== null)
|
|
2593
|
+
.map((loss, index) => normalizeNativeLossRecord(loss, `loss_${index + 1}`));
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
function normalizeNativeLossRecord(loss, fallbackId) {
|
|
2597
|
+
const record = typeof loss === 'string' ? { message: loss } : loss ?? {};
|
|
2598
|
+
const severity = normalizeLossSeverity(record.severity);
|
|
2599
|
+
const kind = normalizeNativeLossKind(record, severity);
|
|
2600
|
+
const category = nativeImportCategoryForLossKind(kind);
|
|
2601
|
+
return {
|
|
2602
|
+
...record,
|
|
2603
|
+
id: String(record.id ?? fallbackId),
|
|
2604
|
+
severity,
|
|
2605
|
+
kind,
|
|
2606
|
+
message: String(record.message ?? `${kind} loss during native import.`),
|
|
2607
|
+
metadata: {
|
|
2608
|
+
lossCategory: category,
|
|
2609
|
+
semanticMergeAdmission: semanticMergeAdmissionForSeverity(severity),
|
|
2610
|
+
dashboardSeverity: severity,
|
|
2611
|
+
...record.metadata
|
|
2612
|
+
}
|
|
1076
2613
|
};
|
|
1077
|
-
return Object.freeze({
|
|
1078
|
-
...summaryInput,
|
|
1079
|
-
capabilities: normalizeStringList(adapter.capabilities),
|
|
1080
|
-
supportedExtensions: normalizeStringList(adapter.supportedExtensions).map((extension) => extension.startsWith('.') ? extension.toLowerCase() : `.${extension.toLowerCase()}`),
|
|
1081
|
-
diagnostics: normalizeAdapterDiagnostics(adapter.diagnostics, summaryInput, {
|
|
1082
|
-
language: adapter.language,
|
|
1083
|
-
parser: String(adapter.parser),
|
|
1084
|
-
parserVersion: adapter.version === undefined ? undefined : String(adapter.version)
|
|
1085
|
-
}, 'adapter')
|
|
1086
|
-
});
|
|
1087
2614
|
}
|
|
1088
2615
|
|
|
1089
|
-
function
|
|
1090
|
-
|
|
1091
|
-
if (
|
|
1092
|
-
|
|
2616
|
+
function normalizeLossSeverity(value) {
|
|
2617
|
+
const severity = String(value ?? 'warning').toLowerCase();
|
|
2618
|
+
if (severity === 'error') return 'error';
|
|
2619
|
+
if (severity === 'info') return 'info';
|
|
2620
|
+
return 'warning';
|
|
1093
2621
|
}
|
|
1094
2622
|
|
|
1095
|
-
function
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
2623
|
+
function normalizeNativeLossKind(loss, severity) {
|
|
2624
|
+
const kind = String(loss.kind ?? '').trim();
|
|
2625
|
+
if (kind) return kind;
|
|
2626
|
+
if (loss.metadata?.diagnosticId || loss.metadata?.diagnosticCode) {
|
|
2627
|
+
return severity === 'error' ? 'unsupportedSyntax' : 'parserDiagnostic';
|
|
2628
|
+
}
|
|
2629
|
+
return severity === 'error' ? 'unsupportedSyntax' : 'opaqueNative';
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
function nativeImportCategoryForLossKind(kind) {
|
|
2633
|
+
if (kind === 'declarationOnlyCoverage') return 'declarationsOnly';
|
|
2634
|
+
if (kind === 'opaqueNative') return 'opaqueBodies';
|
|
2635
|
+
if (kind === 'macroExpansion') return 'macroExpansion';
|
|
2636
|
+
if (kind === 'preprocessor' || kind === 'conditionalCompilation' || kind === 'macroHygiene') return 'preprocessor';
|
|
2637
|
+
if (kind === 'metaprogramming' || kind === 'reflection' || kind === 'dynamicDispatch' || kind === 'dynamicRuntime') return 'metaprogramming';
|
|
2638
|
+
if (kind === 'generatedCode') return 'generatedCode';
|
|
2639
|
+
if (kind === 'sourcePreservation' || kind === 'nonRoundTrippable') return 'sourcePreservation';
|
|
2640
|
+
if (kind === 'parserDiagnostic') return 'parserDiagnostics';
|
|
2641
|
+
if (kind === 'unsupportedSyntax' || kind === 'unsupportedSemantic') return 'unsupportedSyntax';
|
|
2642
|
+
if (kind === 'partialSemanticIndex') return 'partialSemanticIndex';
|
|
2643
|
+
if (kind === 'sourceMapApproximation') return 'sourceMapApproximation';
|
|
2644
|
+
return String(kind ?? 'opaqueNative');
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
function nativeImportLanguageProfile(language, input = {}) {
|
|
2648
|
+
const lossKinds = input.lossKinds ?? ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation'];
|
|
2649
|
+
return Object.freeze({
|
|
2650
|
+
language,
|
|
2651
|
+
aliases: Object.freeze(uniqueStrings(input.aliases ?? [])),
|
|
2652
|
+
extensions: Object.freeze(uniqueStrings(input.extensions ?? [])),
|
|
2653
|
+
supportsLightweightScan: input.supportsLightweightScan !== false,
|
|
2654
|
+
parserAdapters: Object.freeze(uniqueStrings(input.parserAdapters ?? ['tree-sitter'])),
|
|
2655
|
+
projectionTargets: Object.freeze(uniqueStrings(input.projectionTargets ?? FrontierCompileTargets)),
|
|
2656
|
+
knownLossKinds: Object.freeze(uniqueStrings(lossKinds)),
|
|
2657
|
+
defaultReadiness: input.defaultReadiness ?? 'needs-review',
|
|
2658
|
+
notes: Object.freeze(uniqueStrings(input.notes ?? ['lightweight scanner records declarations only; exact parser adapters must be injected by the host']))
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
function mergeNativeImportProfiles(languages, imports, adapters) {
|
|
2663
|
+
const profilesByLanguage = new Map();
|
|
2664
|
+
for (const profile of languages) {
|
|
2665
|
+
const normalized = normalizeNativeLanguageId(profile.language ?? profile);
|
|
2666
|
+
profilesByLanguage.set(normalized, normalizeNativeImportLanguageProfile(profile, normalized));
|
|
2667
|
+
}
|
|
2668
|
+
for (const imported of imports) {
|
|
2669
|
+
const normalized = normalizeNativeLanguageId(imported?.language ?? imported?.nativeAst?.language);
|
|
2670
|
+
if (!normalized || profilesByLanguage.has(normalized)) continue;
|
|
2671
|
+
profilesByLanguage.set(normalized, nativeImportLanguageProfile(normalized, {
|
|
2672
|
+
supportsLightweightScan: false,
|
|
2673
|
+
parserAdapters: [],
|
|
2674
|
+
defaultReadiness: 'blocked',
|
|
2675
|
+
lossKinds: ['unsupportedSyntax'],
|
|
2676
|
+
notes: ['language appeared in import evidence but has no declared Frontier coverage profile']
|
|
2677
|
+
}));
|
|
2678
|
+
}
|
|
2679
|
+
for (const adapter of adapters) {
|
|
2680
|
+
const normalized = normalizeNativeLanguageId(adapter?.language);
|
|
2681
|
+
if (!normalized) continue;
|
|
2682
|
+
const existing = profilesByLanguage.get(normalized) ?? nativeImportLanguageProfile(normalized, { supportsLightweightScan: false, parserAdapters: [] });
|
|
2683
|
+
profilesByLanguage.set(normalized, {
|
|
2684
|
+
...existing,
|
|
2685
|
+
parserAdapters: uniqueStrings([...(existing.parserAdapters ?? []), adapter.parser ?? adapter.id].filter(Boolean))
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
return [...profilesByLanguage.values()].sort((left, right) => left.language.localeCompare(right.language));
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
function normalizeNativeImportLanguageProfile(profile, fallbackLanguage) {
|
|
2692
|
+
const language = normalizeNativeLanguageId(profile.language ?? fallbackLanguage);
|
|
2693
|
+
return {
|
|
2694
|
+
language,
|
|
2695
|
+
aliases: uniqueStrings(profile.aliases ?? []),
|
|
2696
|
+
extensions: uniqueStrings(profile.extensions ?? []),
|
|
2697
|
+
supportsLightweightScan: profile.supportsLightweightScan !== false,
|
|
2698
|
+
parserAdapters: uniqueStrings(profile.parserAdapters ?? []),
|
|
2699
|
+
projectionTargets: uniqueStrings(profile.projectionTargets ?? FrontierCompileTargets),
|
|
2700
|
+
knownLossKinds: uniqueStrings(profile.knownLossKinds ?? profile.lossKinds ?? []),
|
|
2701
|
+
defaultReadiness: profile.defaultReadiness ?? 'needs-review',
|
|
2702
|
+
notes: uniqueStrings(profile.notes ?? [])
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
function nativeImportCoverageForProfile(profile, imports, adapters) {
|
|
2707
|
+
const aliases = new Set([profile.language, ...(profile.aliases ?? [])].map(normalizeNativeLanguageId).filter(Boolean));
|
|
2708
|
+
const matchingImports = imports.filter((imported) => aliases.has(normalizeNativeLanguageId(imported?.language ?? imported?.nativeAst?.language)));
|
|
2709
|
+
const matchingAdapters = adapters.filter((adapter) => aliases.has(normalizeNativeLanguageId(adapter?.language)));
|
|
2710
|
+
const lossSummary = summarizeNativeImportLosses(matchingImports.flatMap((imported) => imported?.losses ?? []), {
|
|
2711
|
+
evidence: matchingImports.flatMap((imported) => imported?.evidence ?? [])
|
|
2712
|
+
});
|
|
2713
|
+
const readiness = matchingImports.length
|
|
2714
|
+
? lossSummary.semanticMergeReadiness
|
|
2715
|
+
: profile.supportsLightweightScan ? profile.defaultReadiness : 'blocked';
|
|
2716
|
+
const importedParsers = uniqueStrings(matchingImports.map((imported) => imported?.nativeAst?.parser ?? imported?.parser ?? imported?.metadata?.parser).filter(Boolean));
|
|
2717
|
+
const sourceMaps = matchingImports.flatMap((imported) => imported?.sourceMaps ?? imported?.universalAst?.sourceMaps ?? []);
|
|
2718
|
+
return {
|
|
2719
|
+
language: profile.language,
|
|
2720
|
+
aliases: profile.aliases,
|
|
2721
|
+
extensions: profile.extensions,
|
|
2722
|
+
supportsLightweightScan: profile.supportsLightweightScan,
|
|
2723
|
+
parserAdapters: uniqueStrings([...(profile.parserAdapters ?? []), ...matchingAdapters.map((adapter) => adapter.parser ?? adapter.id).filter(Boolean)]),
|
|
2724
|
+
projectionTargets: profile.projectionTargets,
|
|
2725
|
+
knownLossKinds: uniqueStrings([...(profile.knownLossKinds ?? []), ...Object.keys(lossSummary.byKind)]),
|
|
2726
|
+
defaultReadiness: profile.defaultReadiness,
|
|
2727
|
+
notes: profile.notes,
|
|
2728
|
+
imports: {
|
|
2729
|
+
total: matchingImports.length,
|
|
2730
|
+
parsers: importedParsers,
|
|
2731
|
+
readiness,
|
|
2732
|
+
readinessReasons: matchingImports.length ? lossSummary.readinessReasons : nativeImportCoverageReasons(profile),
|
|
2733
|
+
symbols: matchingImports.reduce((sum, imported) => sum + (imported?.semanticIndex?.symbols?.length ?? imported?.universalAst?.semanticIndex?.symbols?.length ?? 0), 0),
|
|
2734
|
+
sourceMaps: sourceMaps.length,
|
|
2735
|
+
sourceMapMappings: sourceMaps.reduce((sum, sourceMap) => sum + (sourceMap?.mappings?.length ?? 0), 0),
|
|
2736
|
+
losses: lossSummary.total,
|
|
2737
|
+
lossKinds: lossSummary.byKind,
|
|
2738
|
+
lossCategories: lossSummary.categories
|
|
2739
|
+
}
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
function semanticImportSidecarEntry(imported, index, options) {
|
|
2744
|
+
const semanticIndex = imported?.semanticIndex ?? imported?.universalAst?.semanticIndex;
|
|
2745
|
+
const nativeAst = imported?.nativeAst ?? imported?.nativeSource?.ast;
|
|
2746
|
+
const sourceMaps = imported?.sourceMaps ?? imported?.universalAst?.sourceMaps ?? [];
|
|
2747
|
+
const sourceMapMappings = sourceMaps.flatMap((sourceMap) => sourceMap?.mappings ?? []);
|
|
2748
|
+
const mappingsBySymbolId = new Map();
|
|
2749
|
+
for (const mapping of sourceMapMappings) {
|
|
2750
|
+
if (mapping.semanticSymbolId && !mappingsBySymbolId.has(mapping.semanticSymbolId)) {
|
|
2751
|
+
mappingsBySymbolId.set(mapping.semanticSymbolId, mapping);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
const symbols = [];
|
|
2755
|
+
const regions = [];
|
|
2756
|
+
for (const symbol of semanticIndex?.symbols ?? []) {
|
|
2757
|
+
const mapping = mappingsBySymbolId.get(symbol.id);
|
|
2758
|
+
const nativeNode = symbol.nativeAstNodeId ? nativeAst?.nodes?.[symbol.nativeAstNodeId] : undefined;
|
|
2759
|
+
const region = semanticOwnershipRegionForSymbol(imported, symbol, mapping, nativeNode, options);
|
|
2760
|
+
regions.push(region);
|
|
2761
|
+
symbols.push({
|
|
2762
|
+
id: symbol.id,
|
|
2763
|
+
name: symbol.name,
|
|
2764
|
+
kind: symbol.kind,
|
|
2765
|
+
language: symbol.language ?? imported?.language,
|
|
2766
|
+
nativeAstNodeId: symbol.nativeAstNodeId,
|
|
2767
|
+
semanticOccurrenceId: mapping?.semanticOccurrenceId,
|
|
2768
|
+
sourceMapMappingId: mapping?.id,
|
|
2769
|
+
sourceSpan: mapping?.sourceSpan ?? symbol.definitionSpan ?? nativeNode?.span,
|
|
2770
|
+
signatureHash: symbol.signatureHash,
|
|
2771
|
+
ownershipRegionId: region.id,
|
|
2772
|
+
ownershipKey: region.key,
|
|
2773
|
+
readiness: imported?.metadata?.semanticMergeReadiness ?? imported?.mergeCandidates?.[0]?.readiness ?? 'needs-review'
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
return {
|
|
2777
|
+
id: imported?.id ?? `import_${index + 1}`,
|
|
2778
|
+
language: imported?.language,
|
|
2779
|
+
sourcePath: imported?.sourcePath ?? imported?.nativeSource?.sourcePath ?? nativeAst?.sourcePath,
|
|
2780
|
+
sourceHash: imported?.nativeSource?.sourceHash ?? nativeAst?.sourceHash,
|
|
2781
|
+
parser: imported?.nativeAst?.parser ?? nativeAst?.parser,
|
|
2782
|
+
nativeSourceId: imported?.nativeSource?.id,
|
|
2783
|
+
nativeAstId: nativeAst?.id,
|
|
2784
|
+
semanticIndexId: semanticIndex?.id,
|
|
2785
|
+
universalAstId: imported?.universalAst?.id,
|
|
2786
|
+
symbolCount: symbols.length,
|
|
2787
|
+
sourceMapCount: sourceMaps.length,
|
|
2788
|
+
sourceMapMappingCount: sourceMapMappings.length,
|
|
2789
|
+
readiness: imported?.metadata?.semanticMergeReadiness ?? imported?.mergeCandidates?.[0]?.readiness ?? 'needs-review',
|
|
2790
|
+
emptySemanticIndex: symbols.length === 0,
|
|
2791
|
+
symbols,
|
|
2792
|
+
ownershipRegions: uniqueRecordsById(regions)
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
function semanticOwnershipRegionForSymbol(imported, symbol, mapping, nativeNode, options = {}) {
|
|
2797
|
+
const sourcePath = mapping?.sourceSpan?.path ?? symbol.definitionSpan?.path ?? nativeNode?.span?.path ?? imported?.sourcePath ?? imported?.nativeSource?.sourcePath ?? imported?.nativeAst?.sourcePath;
|
|
2798
|
+
const language = symbol.language ?? imported?.language ?? imported?.nativeAst?.language ?? imported?.nativeSource?.language;
|
|
2799
|
+
const sourceSpan = mapping?.sourceSpan ?? symbol.definitionSpan ?? nativeNode?.span;
|
|
2800
|
+
const key = [
|
|
2801
|
+
options.regionPrefix ?? 'source',
|
|
2802
|
+
sourcePath ?? `${language}:memory`,
|
|
2803
|
+
symbol.kind ?? 'symbol',
|
|
2804
|
+
symbol.name ?? symbol.id
|
|
2805
|
+
].map((part) => String(part).replace(/\s+/g, ' ').trim()).join('#');
|
|
2806
|
+
return {
|
|
2807
|
+
id: `region_${idFragment(key)}`,
|
|
2808
|
+
key,
|
|
2809
|
+
granularity: 'symbol',
|
|
2810
|
+
language,
|
|
2811
|
+
sourcePath,
|
|
2812
|
+
sourceHash: imported?.nativeSource?.sourceHash ?? imported?.nativeAst?.sourceHash,
|
|
2813
|
+
symbolId: symbol.id,
|
|
2814
|
+
symbolName: symbol.name,
|
|
2815
|
+
symbolKind: symbol.kind,
|
|
2816
|
+
nativeAstNodeId: symbol.nativeAstNodeId ?? nativeNode?.id,
|
|
2817
|
+
sourceSpan,
|
|
2818
|
+
precision: mapping?.precision ?? (sourceSpan ? 'declaration' : 'unknown'),
|
|
2819
|
+
mergePolicy: 'single-writer-review-required'
|
|
2820
|
+
};
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
function semanticOwnershipRegionForDeclaration(input, declaration, documentId) {
|
|
2824
|
+
const name = declaration.name ?? declaration.importPath ?? declaration.nodeId ?? declaration.nativeNode?.id;
|
|
2825
|
+
const kind = declaration.symbolKind ?? declaration.kind ?? declaration.nativeNode?.kind ?? 'symbol';
|
|
2826
|
+
const sourcePath = declaration.span?.path ?? declaration.nativeNode?.span?.path ?? input.sourcePath ?? `${input.language}:memory`;
|
|
2827
|
+
const key = ['source', sourcePath, kind, name].map((part) => String(part).replace(/\s+/g, ' ').trim()).join('#');
|
|
2828
|
+
return {
|
|
2829
|
+
id: `region_${idFragment(key)}`,
|
|
2830
|
+
key,
|
|
2831
|
+
granularity: 'symbol',
|
|
2832
|
+
language: input.language,
|
|
2833
|
+
documentId,
|
|
2834
|
+
sourcePath,
|
|
2835
|
+
sourceHash: input.sourceHash,
|
|
2836
|
+
symbolId: declaration.symbolId,
|
|
2837
|
+
symbolName: name,
|
|
2838
|
+
symbolKind: kind,
|
|
2839
|
+
nativeAstNodeId: declaration.nodeId ?? declaration.nativeNode?.id,
|
|
2840
|
+
sourceSpan: declaration.span ?? declaration.nativeNode?.span,
|
|
2841
|
+
precision: declaration.span || declaration.nativeNode?.span ? 'declaration' : 'unknown',
|
|
2842
|
+
mergePolicy: 'single-writer-review-required'
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
function semanticPatchHintForRegion(region, readiness, options = {}) {
|
|
2847
|
+
return {
|
|
2848
|
+
id: `hint_${idFragment(region.id)}`,
|
|
2849
|
+
kind: 'source-region-patch',
|
|
2850
|
+
ownershipRegionId: region.id,
|
|
2851
|
+
ownershipKey: region.key,
|
|
2852
|
+
sourcePath: region.sourcePath,
|
|
2853
|
+
sourceHash: region.sourceHash,
|
|
2854
|
+
sourceSpan: region.sourceSpan,
|
|
2855
|
+
readiness,
|
|
2856
|
+
precision: region.precision,
|
|
2857
|
+
supportedOperations: ['replace-region', 'insert-before-region', 'insert-after-region'],
|
|
2858
|
+
projection: {
|
|
2859
|
+
sourceLanguage: region.language,
|
|
2860
|
+
targetPath: options.targetPath ?? region.sourcePath,
|
|
2861
|
+
requiresSourceMap: true
|
|
2862
|
+
}
|
|
2863
|
+
};
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
function nativeImportCoverageReasons(profile) {
|
|
2867
|
+
if (!profile.supportsLightweightScan) return ['No built-in scanner coverage profile; host must provide an exact adapter or mark unsupported.'];
|
|
2868
|
+
return ['Built-in coverage is declaration-level only; use injected parser adapters for exact AST/CST, tokens, trivia, type resolution, and round-trip evidence.'];
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
function normalizeNativeLanguageId(value) {
|
|
2872
|
+
if (!value) return '';
|
|
2873
|
+
const text = String(value).trim().toLowerCase();
|
|
2874
|
+
if (text === 'js' || text === 'mjs' || text === 'cjs' || text === 'jsx') return 'javascript';
|
|
2875
|
+
if (text === 'ts' || text === 'tsx') return 'typescript';
|
|
2876
|
+
if (text === 'py' || text === 'pyi') return 'python';
|
|
2877
|
+
if (text === 'rs') return 'rust';
|
|
2878
|
+
if (text === 'h') return 'c';
|
|
2879
|
+
if (text === 'c++' || text === 'cc' || text === 'cxx' || text === 'hpp' || text === 'hh') return 'cpp';
|
|
2880
|
+
if (text === 'c#' || text === 'cs') return 'csharp';
|
|
2881
|
+
if (text === 'rb' || text === 'rake') return 'ruby';
|
|
2882
|
+
if (text === 'kt' || text === 'kts') return 'kotlin';
|
|
2883
|
+
if (text === 'sc') return 'scala';
|
|
2884
|
+
if (text === 'sh' || text === 'bash' || text === 'zsh') return 'shell';
|
|
2885
|
+
if (text === 'postgresql' || text === 'postgres' || text === 'mysql' || text === 'sqlite') return 'sql';
|
|
2886
|
+
if (text === 'ex' || text === 'exs') return 'elixir';
|
|
2887
|
+
if (text === 'erl' || text === 'hrl') return 'erlang';
|
|
2888
|
+
if (text === 'hs' || text === 'lhs') return 'haskell';
|
|
2889
|
+
return text;
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
function semanticMergeAdmissionForSeverity(severity) {
|
|
2893
|
+
if (severity === 'error') return 'blocked';
|
|
2894
|
+
if (severity === 'warning') return 'review';
|
|
2895
|
+
return 'disclose';
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
function nativeImportReadinessReasons(input) {
|
|
2899
|
+
if (input.failedEvidenceIds.length) {
|
|
2900
|
+
return [`Failed native import evidence prevents merge: ${input.failedEvidenceIds.join(', ')}`];
|
|
2901
|
+
}
|
|
2902
|
+
if (input.blockingLossIds.length) {
|
|
2903
|
+
return [`Native import error loss(es) block semantic merge: ${input.blockingLossIds.join(', ')}`];
|
|
2904
|
+
}
|
|
2905
|
+
if (input.reviewLossIds.length) {
|
|
2906
|
+
return [`Native import warning loss(es) require review: ${input.reviewLossIds.join(', ')}`];
|
|
2907
|
+
}
|
|
2908
|
+
if (input.informationalLossIds.length) {
|
|
2909
|
+
return [`Native import recorded informational loss(es): ${input.informationalLossIds.join(', ')}`];
|
|
2910
|
+
}
|
|
2911
|
+
if (input.exactAst) return ['Native import supplied exact AST coverage with no recorded loss.'];
|
|
2912
|
+
return ['Native import has no recorded loss.'];
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
function attachNativeImportLossSummary(evidence, lossSummary) {
|
|
2916
|
+
return (evidence ?? []).map((record) => ({
|
|
2917
|
+
...record,
|
|
2918
|
+
metadata: {
|
|
2919
|
+
...record.metadata,
|
|
2920
|
+
nativeImportLossSummary: lossSummary,
|
|
2921
|
+
semanticMergeReadiness: lossSummary.semanticMergeReadiness,
|
|
2922
|
+
lossCategories: lossSummary.categories
|
|
2923
|
+
}
|
|
2924
|
+
}));
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
function withNativeImportReadiness(importResult, lossSummary) {
|
|
2928
|
+
const mergeCandidates = (importResult.mergeCandidates ?? []).map((candidate) => {
|
|
2929
|
+
const readiness = maxSemanticMergeReadiness(candidate.readiness, lossSummary.semanticMergeReadiness);
|
|
2930
|
+
return {
|
|
2931
|
+
...candidate,
|
|
2932
|
+
readiness,
|
|
2933
|
+
reasons: uniqueStrings([
|
|
2934
|
+
...(candidate.reasons ?? []),
|
|
2935
|
+
...lossSummary.readinessReasons
|
|
2936
|
+
]),
|
|
2937
|
+
metadata: {
|
|
2938
|
+
...candidate.metadata,
|
|
2939
|
+
nativeImportLossSummary: lossSummary,
|
|
2940
|
+
severityReadiness: lossSummary.semanticMergeReadiness,
|
|
2941
|
+
finalReadiness: readiness,
|
|
2942
|
+
lossCategories: lossSummary.categories,
|
|
2943
|
+
lossSeverityCounts: lossSummary.bySeverity,
|
|
2944
|
+
lossKindCounts: lossSummary.byKind
|
|
2945
|
+
}
|
|
2946
|
+
};
|
|
2947
|
+
});
|
|
2948
|
+
return {
|
|
2949
|
+
...importResult,
|
|
2950
|
+
mergeCandidates,
|
|
2951
|
+
metadata: {
|
|
2952
|
+
...importResult.metadata,
|
|
2953
|
+
nativeImportLossSummary: lossSummary,
|
|
2954
|
+
semanticMergeReadiness: mergeCandidates[0]?.readiness ?? lossSummary.semanticMergeReadiness,
|
|
2955
|
+
lossCategories: lossSummary.categories,
|
|
2956
|
+
lossSeverityCounts: lossSummary.bySeverity,
|
|
2957
|
+
lossKindCounts: lossSummary.byKind
|
|
2958
|
+
}
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
function maxSemanticMergeReadiness(left, right) {
|
|
2963
|
+
const leftRank = semanticMergeReadinessRank[left] ?? semanticMergeReadinessRank['needs-review'];
|
|
2964
|
+
const rightRank = semanticMergeReadinessRank[right] ?? semanticMergeReadinessRank['needs-review'];
|
|
2965
|
+
return leftRank >= rightRank ? left : right;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
export function createUniversalAstFromDocument(document, input = {}) {
|
|
2969
|
+
return createUniversalAstEnvelope({
|
|
2970
|
+
id: input.id ?? `universal_ast_${idFragment(document.id)}`,
|
|
2971
|
+
document,
|
|
2972
|
+
semanticIndex: input.semanticIndex,
|
|
2973
|
+
sourceMaps: input.sourceMaps ?? [],
|
|
2974
|
+
evidence: input.evidence ?? [],
|
|
2975
|
+
metadata: input.metadata
|
|
2976
|
+
});
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
export function readUniversalAstJson(source) {
|
|
2980
|
+
const envelope = JSON.parse(source);
|
|
2981
|
+
const issues = validateUniversalAstEnvelope(envelope);
|
|
2982
|
+
if (issues.length > 0) {
|
|
2983
|
+
throw new Error(`Invalid Frontier universal AST JSON: ${issues.join('; ')}`);
|
|
2984
|
+
}
|
|
2985
|
+
return envelope;
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
export function writeUniversalAstJson(envelope) {
|
|
2989
|
+
const issues = validateUniversalAstEnvelope(envelope);
|
|
2990
|
+
if (issues.length > 0) {
|
|
2991
|
+
throw new Error(`Invalid Frontier universal AST envelope: ${issues.join('; ')}`);
|
|
2992
|
+
}
|
|
2993
|
+
return stableUniversalAstJson(envelope);
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
export function emitForTarget(document, target = 'typescript', options = {}) {
|
|
2997
|
+
return renderTargetAst(projectFrontierAst(document, target, options), target);
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
function createJavaScriptSyntaxImporterAdapter(options) {
|
|
3001
|
+
return {
|
|
3002
|
+
id: options.id,
|
|
3003
|
+
language: options.language,
|
|
3004
|
+
parser: options.parser,
|
|
3005
|
+
version: options.version,
|
|
3006
|
+
capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
|
|
3007
|
+
supportedExtensions: options.supportedExtensions,
|
|
3008
|
+
diagnostics: options.diagnostics,
|
|
3009
|
+
parse(input) {
|
|
3010
|
+
const ast = input.options?.ast ?? input.options?.nativeAst ?? options.ast ?? parseJavaScriptSyntax(input, options);
|
|
3011
|
+
if (!ast) {
|
|
3012
|
+
return missingInjectedParserResult(input, {
|
|
3013
|
+
parser: options.parser,
|
|
3014
|
+
adapterId: options.id,
|
|
3015
|
+
message: `${options.id} requires an injected parse function, parserModule.parse, ast, or adapterOptions.ast.`
|
|
3016
|
+
});
|
|
3017
|
+
}
|
|
3018
|
+
const parseDiagnostics = normalizeParserErrors(ast.errors, input, options);
|
|
3019
|
+
return createNativeImportFromSyntaxAst(ast, input, {
|
|
3020
|
+
parser: options.parser,
|
|
3021
|
+
astFormat: options.astFormat,
|
|
3022
|
+
maxNodes: options.maxNodes,
|
|
3023
|
+
diagnostics: parseDiagnostics
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
function parseJavaScriptSyntax(input, options) {
|
|
3030
|
+
const parse = options.parse ?? options.parserModule?.parse ?? options.babelParser?.parse;
|
|
3031
|
+
if (typeof parse !== 'function') return undefined;
|
|
3032
|
+
const parserOptions = {
|
|
3033
|
+
sourceFilename: input.sourcePath,
|
|
3034
|
+
...(options.defaultParserOptions ?? {}),
|
|
3035
|
+
...(typeof options.parserOptions === 'function'
|
|
3036
|
+
? options.parserOptions(input)
|
|
3037
|
+
: options.parserOptions ?? {}),
|
|
3038
|
+
...(input.options?.parserOptions ?? {})
|
|
3039
|
+
};
|
|
3040
|
+
return parse(input.sourceText, parserOptions);
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
function createTypeScriptSourceFile(ts, input, options) {
|
|
3044
|
+
if (typeof options.createSourceFile === 'function') return options.createSourceFile(input, ts);
|
|
3045
|
+
if (!ts || typeof ts.createSourceFile !== 'function') return undefined;
|
|
3046
|
+
const scriptTarget = options.scriptTarget ?? ts.ScriptTarget?.Latest ?? ts.ScriptTarget?.ESNext ?? 99;
|
|
3047
|
+
const scriptKind = options.scriptKind ?? inferTypeScriptScriptKind(ts, input);
|
|
3048
|
+
return ts.createSourceFile(input.sourcePath ?? 'frontier-input.ts', input.sourceText, scriptTarget, true, scriptKind);
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
function inferTypeScriptScriptKind(ts, input) {
|
|
3052
|
+
const path = String(input.sourcePath ?? '').toLowerCase();
|
|
3053
|
+
if (path.endsWith('.tsx')) return ts.ScriptKind?.TSX;
|
|
3054
|
+
if (path.endsWith('.jsx')) return ts.ScriptKind?.JSX;
|
|
3055
|
+
if (path.endsWith('.js') || path.endsWith('.mjs') || path.endsWith('.cjs')) return ts.ScriptKind?.JS;
|
|
3056
|
+
return ts.ScriptKind?.TS;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
function parseTreeSitterSource(input, options) {
|
|
3060
|
+
const parser = options.parserInstance ?? options.treeSitterParser ?? options.parser;
|
|
3061
|
+
if (parser && typeof parser.parse === 'function') return parser.parse(input.sourceText);
|
|
3062
|
+
if (typeof options.parse === 'function') return options.parse(input);
|
|
3063
|
+
return undefined;
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
function createNativeImportFromSyntaxAst(ast, input, options) {
|
|
3067
|
+
const root = normalizeSyntaxAstRoot(ast, options.astFormat);
|
|
3068
|
+
if (!root) {
|
|
3069
|
+
return missingInjectedParserResult(input, {
|
|
3070
|
+
parser: options.parser,
|
|
3071
|
+
adapterId: input.adapterId,
|
|
3072
|
+
message: 'Injected AST did not contain an object root node.'
|
|
3073
|
+
});
|
|
3074
|
+
}
|
|
3075
|
+
const context = createAstNormalizationContext(input, options);
|
|
3076
|
+
visitSyntaxAstNode(root, context, 'root');
|
|
3077
|
+
if (context.truncated) {
|
|
3078
|
+
context.losses.push(truncatedAstLoss(input, context, options));
|
|
3079
|
+
}
|
|
3080
|
+
const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
|
|
3081
|
+
return {
|
|
3082
|
+
rootId: context.rootId,
|
|
3083
|
+
nodes: context.nodes,
|
|
3084
|
+
semanticIndex: semantic.semanticIndex,
|
|
3085
|
+
mappings: semantic.mappings,
|
|
3086
|
+
losses: mergeNativeLosses(context.losses, options.diagnostics?.map((diagnostic, index) => adapterDiagnosticToLoss(diagnostic, index, {
|
|
3087
|
+
id: input.adapterId,
|
|
3088
|
+
version: input.adapterVersion
|
|
3089
|
+
}, input)) ?? []),
|
|
3090
|
+
evidence: semantic.evidence,
|
|
3091
|
+
diagnostics: options.diagnostics,
|
|
3092
|
+
metadata: {
|
|
3093
|
+
astFormat: options.astFormat,
|
|
3094
|
+
parser: options.parser,
|
|
3095
|
+
normalizedNodeCount: Object.keys(context.nodes).length,
|
|
3096
|
+
declarationCount: context.declarations.length,
|
|
3097
|
+
truncated: context.truncated
|
|
3098
|
+
}
|
|
3099
|
+
};
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
function createNativeImportFromTypeScriptAst(sourceFile, input, options) {
|
|
3103
|
+
const context = createAstNormalizationContext(input, options);
|
|
3104
|
+
visitTypeScriptAstNode(sourceFile, sourceFile, context, 'root', options.ts);
|
|
3105
|
+
if (context.truncated) {
|
|
3106
|
+
context.losses.push(truncatedAstLoss(input, context, options));
|
|
3107
|
+
}
|
|
3108
|
+
const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
|
|
3109
|
+
return {
|
|
3110
|
+
rootId: context.rootId,
|
|
3111
|
+
nodes: context.nodes,
|
|
3112
|
+
semanticIndex: semantic.semanticIndex,
|
|
3113
|
+
mappings: semantic.mappings,
|
|
3114
|
+
losses: context.losses,
|
|
3115
|
+
evidence: semantic.evidence,
|
|
3116
|
+
metadata: {
|
|
3117
|
+
astFormat: options.astFormat,
|
|
3118
|
+
parser: options.parser,
|
|
3119
|
+
normalizedNodeCount: Object.keys(context.nodes).length,
|
|
3120
|
+
declarationCount: context.declarations.length,
|
|
3121
|
+
truncated: context.truncated
|
|
3122
|
+
}
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
function createNativeImportFromTreeSitter(root, input, options) {
|
|
3127
|
+
const context = createAstNormalizationContext(input, options);
|
|
3128
|
+
visitTreeSitterNode(root, context, 'root');
|
|
3129
|
+
if (context.truncated) {
|
|
3130
|
+
context.losses.push(truncatedAstLoss(input, context, options));
|
|
3131
|
+
}
|
|
3132
|
+
const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
|
|
3133
|
+
return {
|
|
3134
|
+
rootId: context.rootId,
|
|
3135
|
+
nodes: context.nodes,
|
|
3136
|
+
semanticIndex: semantic.semanticIndex,
|
|
3137
|
+
mappings: semantic.mappings,
|
|
3138
|
+
losses: context.losses,
|
|
3139
|
+
evidence: semantic.evidence,
|
|
3140
|
+
metadata: {
|
|
3141
|
+
astFormat: options.astFormat,
|
|
3142
|
+
parser: options.parser,
|
|
3143
|
+
normalizedNodeCount: Object.keys(context.nodes).length,
|
|
3144
|
+
declarationCount: context.declarations.length,
|
|
3145
|
+
truncated: context.truncated
|
|
3146
|
+
}
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
function createAstNormalizationContext(input, options) {
|
|
3151
|
+
return {
|
|
3152
|
+
input,
|
|
3153
|
+
options,
|
|
3154
|
+
maxNodes: Number.isFinite(options.maxNodes) ? Math.max(1, options.maxNodes) : 5000,
|
|
3155
|
+
counter: 0,
|
|
3156
|
+
objectIds: new WeakMap(),
|
|
3157
|
+
nodes: {},
|
|
3158
|
+
declarations: [],
|
|
3159
|
+
losses: [],
|
|
3160
|
+
rootId: undefined,
|
|
3161
|
+
truncated: false
|
|
3162
|
+
};
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
function normalizeSyntaxAstRoot(ast, astFormat) {
|
|
3166
|
+
if (!ast || typeof ast !== 'object') return undefined;
|
|
3167
|
+
if (astFormat === 'babel' && ast.program && typeof ast.program === 'object') return ast.program;
|
|
3168
|
+
return ast;
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
function visitSyntaxAstNode(node, context, propertyPath) {
|
|
3172
|
+
if (!isSyntaxAstNode(node) || context.truncated) return undefined;
|
|
3173
|
+
if (context.objectIds.has(node)) return context.objectIds.get(node);
|
|
3174
|
+
if (context.counter >= context.maxNodes) {
|
|
3175
|
+
context.truncated = true;
|
|
3176
|
+
return undefined;
|
|
3177
|
+
}
|
|
3178
|
+
const id = nativeNodeId(context, node.type ?? node.kind ?? 'Node', node.loc, propertyPath);
|
|
3179
|
+
context.objectIds.set(node, id);
|
|
3180
|
+
if (!context.rootId) context.rootId = id;
|
|
3181
|
+
const children = [];
|
|
3182
|
+
const fields = primitiveSyntaxFields(node);
|
|
3183
|
+
for (const [key, value] of Object.entries(node)) {
|
|
3184
|
+
if (ignoredSyntaxField(key)) continue;
|
|
3185
|
+
if (Array.isArray(value)) {
|
|
3186
|
+
value.forEach((entry, index) => {
|
|
3187
|
+
const childId = visitSyntaxAstNode(entry, context, `${propertyPath}.${key}[${index}]`);
|
|
3188
|
+
if (childId) children.push(childId);
|
|
3189
|
+
});
|
|
3190
|
+
} else {
|
|
3191
|
+
const childId = visitSyntaxAstNode(value, context, `${propertyPath}.${key}`);
|
|
3192
|
+
if (childId) children.push(childId);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
const declaration = syntaxDeclaration(node, id, context.input, context.options);
|
|
3196
|
+
const nativeNode = {
|
|
3197
|
+
id,
|
|
3198
|
+
kind: String(node.type ?? node.kind ?? 'Node'),
|
|
3199
|
+
languageKind: `${context.input.language}.${node.type ?? node.kind ?? 'Node'}`,
|
|
3200
|
+
span: spanFromLoc(node.loc, context.input),
|
|
3201
|
+
value: declaration?.name ?? literalSyntaxValue(node),
|
|
3202
|
+
fields,
|
|
3203
|
+
children,
|
|
3204
|
+
metadata: {
|
|
3205
|
+
astFormat: context.options.astFormat,
|
|
3206
|
+
propertyPath,
|
|
3207
|
+
start: numberOrUndefined(node.start),
|
|
3208
|
+
end: numberOrUndefined(node.end),
|
|
3209
|
+
range: Array.isArray(node.range) ? node.range.slice(0, 2) : undefined
|
|
3210
|
+
}
|
|
3211
|
+
};
|
|
3212
|
+
context.nodes[id] = nativeNode;
|
|
3213
|
+
if (declaration) context.declarations.push({ ...declaration, nativeNode });
|
|
3214
|
+
return id;
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
function visitTypeScriptAstNode(node, sourceFile, context, propertyPath, ts) {
|
|
3218
|
+
if (!node || typeof node !== 'object' || context.truncated) return undefined;
|
|
3219
|
+
if (context.objectIds.has(node)) return context.objectIds.get(node);
|
|
3220
|
+
if (context.counter >= context.maxNodes) {
|
|
3221
|
+
context.truncated = true;
|
|
3222
|
+
return undefined;
|
|
3223
|
+
}
|
|
3224
|
+
const kind = typeScriptKindName(node, ts);
|
|
3225
|
+
const span = spanFromTypeScriptNode(node, sourceFile);
|
|
3226
|
+
const id = nativeNodeId(context, kind, { start: { line: span?.startLine, column: span?.startColumn } }, propertyPath);
|
|
3227
|
+
context.objectIds.set(node, id);
|
|
3228
|
+
if (!context.rootId) context.rootId = id;
|
|
3229
|
+
const children = [];
|
|
3230
|
+
const visitChild = (child) => {
|
|
3231
|
+
const childId = visitTypeScriptAstNode(child, sourceFile, context, `${propertyPath}.${children.length}`, ts);
|
|
3232
|
+
if (childId) children.push(childId);
|
|
3233
|
+
};
|
|
3234
|
+
if (ts && typeof ts.forEachChild === 'function') {
|
|
3235
|
+
ts.forEachChild(node, visitChild);
|
|
3236
|
+
} else if (typeof node.forEachChild === 'function') {
|
|
3237
|
+
node.forEachChild(visitChild);
|
|
3238
|
+
} else if (Array.isArray(node.children)) {
|
|
3239
|
+
node.children.forEach(visitChild);
|
|
3240
|
+
}
|
|
3241
|
+
const declaration = typeScriptDeclaration(node, kind, id, context.input, context.options);
|
|
3242
|
+
const nativeNode = {
|
|
3243
|
+
id,
|
|
3244
|
+
kind,
|
|
3245
|
+
languageKind: `${context.input.language}.${kind}`,
|
|
3246
|
+
span,
|
|
3247
|
+
value: declaration?.name ?? typeScriptNodeValue(node),
|
|
3248
|
+
fields: primitiveTypeScriptFields(node, kind),
|
|
3249
|
+
children,
|
|
3250
|
+
metadata: {
|
|
3251
|
+
astFormat: context.options.astFormat,
|
|
3252
|
+
propertyPath,
|
|
3253
|
+
pos: numberOrUndefined(node.pos),
|
|
3254
|
+
end: numberOrUndefined(node.end)
|
|
3255
|
+
}
|
|
3256
|
+
};
|
|
3257
|
+
context.nodes[id] = nativeNode;
|
|
3258
|
+
if (declaration) context.declarations.push({ ...declaration, nativeNode });
|
|
3259
|
+
return id;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
function visitTreeSitterNode(node, context, propertyPath) {
|
|
3263
|
+
if (!node || typeof node !== 'object' || context.truncated) return undefined;
|
|
3264
|
+
if (context.objectIds.has(node)) return context.objectIds.get(node);
|
|
3265
|
+
if (context.counter >= context.maxNodes) {
|
|
3266
|
+
context.truncated = true;
|
|
3267
|
+
return undefined;
|
|
3268
|
+
}
|
|
3269
|
+
const kind = String(node.type ?? node.kind ?? 'node');
|
|
3270
|
+
const span = spanFromTreeSitterNode(node, context.input);
|
|
3271
|
+
const id = nativeNodeId(context, kind, { start: { line: span?.startLine, column: span?.startColumn } }, propertyPath);
|
|
3272
|
+
context.objectIds.set(node, id);
|
|
3273
|
+
if (!context.rootId) context.rootId = id;
|
|
3274
|
+
const children = [];
|
|
3275
|
+
const rawChildren = Array.isArray(node.namedChildren)
|
|
3276
|
+
? node.namedChildren
|
|
3277
|
+
: Array.isArray(node.children)
|
|
3278
|
+
? node.children
|
|
3279
|
+
: [];
|
|
3280
|
+
rawChildren.forEach((child, index) => {
|
|
3281
|
+
const childId = visitTreeSitterNode(child, context, `${propertyPath}.children[${index}]`);
|
|
3282
|
+
if (childId) children.push(childId);
|
|
3283
|
+
});
|
|
3284
|
+
const declaration = treeSitterDeclaration(node, kind, id, context.input, context.options);
|
|
3285
|
+
const nativeNode = {
|
|
3286
|
+
id,
|
|
3287
|
+
kind,
|
|
3288
|
+
languageKind: `${context.input.language}.${kind}`,
|
|
3289
|
+
span,
|
|
3290
|
+
value: declaration?.name ?? shortNodeText(node),
|
|
3291
|
+
fields: {
|
|
3292
|
+
named: Boolean(node.isNamed ?? node.named),
|
|
3293
|
+
missing: Boolean(node.isMissing),
|
|
3294
|
+
error: Boolean(node.hasError || kind === 'ERROR')
|
|
3295
|
+
},
|
|
3296
|
+
children,
|
|
3297
|
+
metadata: {
|
|
3298
|
+
astFormat: context.options.astFormat,
|
|
3299
|
+
propertyPath,
|
|
3300
|
+
startIndex: numberOrUndefined(node.startIndex),
|
|
3301
|
+
endIndex: numberOrUndefined(node.endIndex)
|
|
3302
|
+
}
|
|
3303
|
+
};
|
|
3304
|
+
context.nodes[id] = nativeNode;
|
|
3305
|
+
if (declaration) context.declarations.push({ ...declaration, nativeNode });
|
|
3306
|
+
if (node.hasError || kind === 'ERROR') {
|
|
3307
|
+
context.losses.push({
|
|
3308
|
+
id: `loss_${idFragment(id)}_tree_sitter_error`,
|
|
3309
|
+
severity: 'error',
|
|
3310
|
+
phase: 'parse',
|
|
3311
|
+
sourceFormat: context.input.language,
|
|
3312
|
+
kind: 'unsupportedSyntax',
|
|
3313
|
+
message: 'Tree-sitter reported a parse error node.',
|
|
3314
|
+
span,
|
|
3315
|
+
nodeId: id
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
return id;
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
function semanticIndexFromNativeDeclarations(declarations, input, options) {
|
|
3322
|
+
const documentId = `doc_${idFragment(input.sourcePath ?? input.language)}_${idFragment(input.sourceHash)}`;
|
|
3323
|
+
const evidenceId = `evidence_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}_import`;
|
|
3324
|
+
const symbols = [];
|
|
3325
|
+
const occurrences = [];
|
|
3326
|
+
const relations = [];
|
|
3327
|
+
const facts = [];
|
|
3328
|
+
const mappings = [];
|
|
3329
|
+
for (const declaration of declarations) {
|
|
3330
|
+
const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${idFragment(declaration.name)}`;
|
|
3331
|
+
const occurrenceId = `occ_${idFragment(declaration.nativeNode.id)}_${declaration.role ?? 'definition'}`;
|
|
3332
|
+
const ownershipRegion = semanticOwnershipRegionForDeclaration(input, {
|
|
3333
|
+
...declaration,
|
|
3334
|
+
nodeId: declaration.nativeNode.id,
|
|
3335
|
+
kind: declaration.nativeNode.kind,
|
|
3336
|
+
languageKind: declaration.nativeNode.languageKind,
|
|
3337
|
+
span: declaration.nativeNode.span,
|
|
3338
|
+
symbolId
|
|
3339
|
+
}, documentId);
|
|
3340
|
+
declaration.nativeNode.metadata = {
|
|
3341
|
+
...declaration.nativeNode.metadata,
|
|
3342
|
+
ownershipRegionId: ownershipRegion.id,
|
|
3343
|
+
ownershipRegionKey: ownershipRegion.key
|
|
3344
|
+
};
|
|
3345
|
+
symbols.push({
|
|
3346
|
+
id: symbolId,
|
|
3347
|
+
scheme: 'frontier',
|
|
3348
|
+
name: declaration.name,
|
|
3349
|
+
kind: declaration.symbolKind,
|
|
3350
|
+
language: input.language,
|
|
3351
|
+
nativeAstNodeId: declaration.nativeNode.id,
|
|
3352
|
+
signatureHash: hashSemanticValue([input.language, declaration.nativeNode.kind, declaration.name, declaration.nativeNode.fields ?? {}]),
|
|
3353
|
+
definitionSpan: declaration.nativeNode.span,
|
|
3354
|
+
metadata: {
|
|
3355
|
+
ownershipRegionId: ownershipRegion.id,
|
|
3356
|
+
ownershipRegionKey: ownershipRegion.key
|
|
3357
|
+
}
|
|
3358
|
+
});
|
|
3359
|
+
occurrences.push({
|
|
3360
|
+
id: occurrenceId,
|
|
3361
|
+
documentId,
|
|
3362
|
+
symbolId,
|
|
3363
|
+
role: declaration.role ?? 'definition',
|
|
3364
|
+
span: declaration.nativeNode.span,
|
|
3365
|
+
nativeAstNodeId: declaration.nativeNode.id
|
|
3366
|
+
});
|
|
3367
|
+
relations.push({
|
|
3368
|
+
id: `rel_${idFragment(documentId)}_${idFragment(declaration.nativeNode.id)}`,
|
|
3369
|
+
sourceId: documentId,
|
|
3370
|
+
predicate: relationPredicateForDeclaration(declaration),
|
|
3371
|
+
targetId: symbolId
|
|
3372
|
+
});
|
|
3373
|
+
facts.push({
|
|
3374
|
+
id: `fact_${idFragment(declaration.nativeNode.id)}_kind`,
|
|
3375
|
+
predicate: 'nativeKind',
|
|
3376
|
+
subjectId: symbolId,
|
|
3377
|
+
value: declaration.nativeNode.languageKind
|
|
3378
|
+
}, {
|
|
3379
|
+
id: `fact_${idFragment(declaration.nativeNode.id)}_ownership_region`,
|
|
3380
|
+
predicate: 'semanticOwnershipRegion',
|
|
3381
|
+
subjectId: symbolId,
|
|
3382
|
+
value: ownershipRegion
|
|
3383
|
+
});
|
|
3384
|
+
mappings.push({
|
|
3385
|
+
id: `map_${idFragment(declaration.nativeNode.id)}`,
|
|
3386
|
+
nativeAstNodeId: declaration.nativeNode.id,
|
|
3387
|
+
semanticSymbolId: symbolId,
|
|
3388
|
+
semanticOccurrenceId: occurrenceId,
|
|
3389
|
+
sourceSpan: declaration.nativeNode.span,
|
|
3390
|
+
evidenceIds: [evidenceId],
|
|
3391
|
+
lossIds: [],
|
|
3392
|
+
ownershipRegionId: ownershipRegion.id,
|
|
3393
|
+
precision: declaration.nativeNode.span ? 'declaration' : 'unknown'
|
|
3394
|
+
});
|
|
3395
|
+
}
|
|
3396
|
+
const evidence = [{
|
|
3397
|
+
id: evidenceId,
|
|
3398
|
+
kind: 'import',
|
|
3399
|
+
status: 'passed',
|
|
3400
|
+
path: input.sourcePath,
|
|
3401
|
+
summary: `Normalized ${options.astFormat ?? options.parser} native AST with ${declarations.length} declaration(s).`,
|
|
3402
|
+
metadata: {
|
|
3403
|
+
parser: options.parser,
|
|
3404
|
+
astFormat: options.astFormat,
|
|
3405
|
+
language: input.language,
|
|
3406
|
+
sourceHash: input.sourceHash
|
|
3407
|
+
}
|
|
3408
|
+
}];
|
|
3409
|
+
return {
|
|
3410
|
+
semanticIndex: createSemanticIndexRecord({
|
|
3411
|
+
id: `index_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}`,
|
|
3412
|
+
documents: [{
|
|
3413
|
+
id: documentId,
|
|
3414
|
+
path: input.sourcePath ?? `${input.language}:memory`,
|
|
3415
|
+
language: input.language,
|
|
3416
|
+
sourceHash: input.sourceHash
|
|
3417
|
+
}],
|
|
3418
|
+
symbols,
|
|
3419
|
+
occurrences,
|
|
3420
|
+
relations,
|
|
3421
|
+
facts,
|
|
3422
|
+
evidence,
|
|
3423
|
+
metadata: {
|
|
3424
|
+
parser: options.parser,
|
|
3425
|
+
astFormat: options.astFormat,
|
|
3426
|
+
coverage: 'native-ast-declarations'
|
|
3427
|
+
}
|
|
3428
|
+
}),
|
|
3429
|
+
mappings,
|
|
3430
|
+
evidence
|
|
3431
|
+
};
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
function createNativeProjectImportResult(input, imports) {
|
|
3435
|
+
const idPart = idFragment(input.id ?? input.projectRoot ?? 'native_project');
|
|
3436
|
+
const nodes = {};
|
|
3437
|
+
const rootIds = [];
|
|
3438
|
+
const semanticIndex = mergeSemanticIndexes(imports, input, idPart);
|
|
3439
|
+
const nativeSources = [];
|
|
3440
|
+
const sourceMaps = [];
|
|
3441
|
+
const losses = [];
|
|
3442
|
+
const evidence = [];
|
|
3443
|
+
const mergeCandidates = [];
|
|
3444
|
+
const operations = [];
|
|
3445
|
+
for (const result of imports) {
|
|
3446
|
+
for (const node of Object.values(result.document?.nodes ?? {})) {
|
|
3447
|
+
nodes[node.id] = node;
|
|
3448
|
+
}
|
|
3449
|
+
rootIds.push(...(result.document?.rootIds ?? []));
|
|
3450
|
+
if (result.nativeSource) nativeSources.push(result.nativeSource);
|
|
3451
|
+
sourceMaps.push(...(result.sourceMaps ?? []));
|
|
3452
|
+
losses.push(...(result.losses ?? []));
|
|
3453
|
+
evidence.push(...(result.evidence ?? []));
|
|
3454
|
+
mergeCandidates.push(...(result.mergeCandidates ?? []));
|
|
3455
|
+
operations.push(...(result.patch?.operations ?? []));
|
|
3456
|
+
}
|
|
3457
|
+
const document = createDocument({
|
|
3458
|
+
id: input.documentId ?? `document_${idPart}`,
|
|
3459
|
+
name: input.documentName ?? input.name ?? 'NativeProject',
|
|
3460
|
+
nodes: Object.values(nodes),
|
|
3461
|
+
rootIds: uniqueStrings(rootIds),
|
|
3462
|
+
metadata: {
|
|
3463
|
+
sourceLanguage: input.language ?? 'mixed',
|
|
3464
|
+
semanticStatus: losses.some((loss) => loss.severity === 'error') ? 'partial' : 'mapped',
|
|
3465
|
+
projectRoot: input.projectRoot,
|
|
3466
|
+
sourceCount: imports.length,
|
|
3467
|
+
...input.documentMetadata
|
|
3468
|
+
}
|
|
3469
|
+
});
|
|
3470
|
+
const universalAst = createUniversalAstEnvelope({
|
|
3471
|
+
id: input.universalAstId ?? `universal_ast_${idPart}`,
|
|
3472
|
+
document,
|
|
3473
|
+
nativeSources,
|
|
3474
|
+
semanticIndex,
|
|
3475
|
+
sourceMaps,
|
|
3476
|
+
losses: uniqueByLossId(losses),
|
|
3477
|
+
evidence: uniqueByEvidenceId(evidence),
|
|
3478
|
+
metadata: {
|
|
3479
|
+
sourceLanguage: input.language ?? 'mixed',
|
|
3480
|
+
projectRoot: input.projectRoot,
|
|
3481
|
+
sourceCount: imports.length,
|
|
3482
|
+
...input.universalAstMetadata
|
|
3483
|
+
}
|
|
3484
|
+
});
|
|
3485
|
+
const patch = createPatch({
|
|
3486
|
+
id: input.patchId ?? `patch_${idPart}_project_import`,
|
|
3487
|
+
author: input.author ?? '@shapeshift-labs/frontier-lang-compiler/importNativeProject',
|
|
3488
|
+
risk: losses.some((loss) => loss.severity === 'error') ? 'high' : losses.some((loss) => loss.severity === 'warning') ? 'medium' : 'low',
|
|
3489
|
+
operations,
|
|
3490
|
+
evidence: uniqueByEvidenceId(evidence),
|
|
3491
|
+
metadata: {
|
|
3492
|
+
semanticIndexId: semanticIndex?.id,
|
|
3493
|
+
universalAstId: universalAst.id,
|
|
3494
|
+
sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
|
|
3495
|
+
sourceCount: imports.length
|
|
3496
|
+
}
|
|
3497
|
+
});
|
|
3498
|
+
return {
|
|
3499
|
+
kind: 'frontier.lang.projectImportResult',
|
|
3500
|
+
version: 1,
|
|
3501
|
+
id: input.id ?? `project_import_${idPart}`,
|
|
3502
|
+
language: input.language ?? 'mixed',
|
|
3503
|
+
projectRoot: input.projectRoot,
|
|
3504
|
+
imports,
|
|
3505
|
+
document,
|
|
3506
|
+
patch,
|
|
3507
|
+
nativeSources,
|
|
3508
|
+
semanticIndex,
|
|
3509
|
+
universalAst,
|
|
3510
|
+
sourceMaps,
|
|
3511
|
+
losses: uniqueByLossId(losses),
|
|
3512
|
+
evidence: uniqueByEvidenceId(evidence),
|
|
3513
|
+
mergeCandidates,
|
|
3514
|
+
metadata: {
|
|
3515
|
+
sourceCount: imports.length,
|
|
3516
|
+
sourcePaths: imports.map((result) => result.sourcePath).filter(Boolean),
|
|
3517
|
+
...input.metadata
|
|
3518
|
+
}
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
function mergeSemanticIndexes(imports, input, idPart) {
|
|
3523
|
+
const indexes = imports.map((result) => result.semanticIndex ?? result.universalAst?.semanticIndex).filter(Boolean);
|
|
3524
|
+
if (!indexes.length) return undefined;
|
|
3525
|
+
return createSemanticIndexRecord({
|
|
3526
|
+
id: input.semanticIndexId ?? `index_${idPart}_project`,
|
|
3527
|
+
documents: indexes.flatMap((index) => index.documents ?? []),
|
|
3528
|
+
symbols: indexes.flatMap((index) => index.symbols ?? []),
|
|
3529
|
+
occurrences: indexes.flatMap((index) => index.occurrences ?? []),
|
|
3530
|
+
relations: indexes.flatMap((index) => index.relations ?? []),
|
|
3531
|
+
facts: indexes.flatMap((index) => index.facts ?? []),
|
|
3532
|
+
evidence: indexes.flatMap((index) => index.evidence ?? []),
|
|
3533
|
+
metadata: {
|
|
3534
|
+
projectRoot: input.projectRoot,
|
|
3535
|
+
sourceCount: imports.length,
|
|
3536
|
+
mergedIndexCount: indexes.length
|
|
3537
|
+
}
|
|
3538
|
+
});
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
function resolveNativeProjectAdapter(source, adapters, input) {
|
|
3542
|
+
if (typeof input.adapterResolver === 'function') return input.adapterResolver(source, adapters);
|
|
3543
|
+
const language = source.language;
|
|
3544
|
+
const sourcePath = source.sourcePath ?? '';
|
|
3545
|
+
return adapters.find((adapter) => {
|
|
3546
|
+
if (source.adapter && adapter.id === source.adapter) return true;
|
|
3547
|
+
if (language && adapter.language !== language) return false;
|
|
3548
|
+
const extensions = adapter.supportedExtensions ?? [];
|
|
3549
|
+
return !extensions.length || extensions.some((extension) => sourcePath.toLowerCase().endsWith(extension.toLowerCase()));
|
|
3550
|
+
});
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
function normalizeNativeImporterAdapter(adapter) {
|
|
3554
|
+
if (!adapter || typeof adapter !== 'object') {
|
|
3555
|
+
throw new Error('Native importer adapter must be an object');
|
|
3556
|
+
}
|
|
3557
|
+
if (!adapter.id) throw new Error('Native importer adapter requires an id');
|
|
3558
|
+
if (!adapter.language) throw new Error(`Native importer adapter ${adapter.id} requires a language`);
|
|
3559
|
+
if (!adapter.parser) throw new Error(`Native importer adapter ${adapter.id} requires a parser`);
|
|
3560
|
+
if (typeof adapter.parse !== 'function') throw new Error(`Native importer adapter ${adapter.id} requires a parse function`);
|
|
3561
|
+
const summaryInput = {
|
|
3562
|
+
id: String(adapter.id),
|
|
3563
|
+
language: adapter.language,
|
|
3564
|
+
parser: String(adapter.parser),
|
|
3565
|
+
version: adapter.version === undefined ? undefined : String(adapter.version)
|
|
3566
|
+
};
|
|
3567
|
+
return Object.freeze({
|
|
3568
|
+
...summaryInput,
|
|
3569
|
+
capabilities: normalizeStringList(adapter.capabilities),
|
|
3570
|
+
supportedExtensions: normalizeStringList(adapter.supportedExtensions).map((extension) => extension.startsWith('.') ? extension.toLowerCase() : `.${extension.toLowerCase()}`),
|
|
3571
|
+
diagnostics: normalizeAdapterDiagnostics(adapter.diagnostics, summaryInput, {
|
|
3572
|
+
language: adapter.language,
|
|
3573
|
+
parser: String(adapter.parser),
|
|
3574
|
+
parserVersion: adapter.version === undefined ? undefined : String(adapter.version)
|
|
3575
|
+
}, 'adapter')
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
function normalizeStringList(value) {
|
|
3580
|
+
if (value === undefined || value === null) return [];
|
|
3581
|
+
if (Array.isArray(value)) return value.map((item) => String(item)).filter(Boolean);
|
|
3582
|
+
return [String(value)].filter(Boolean);
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
function normalizeAdapterDiagnostics(value, adapter, input, scope = 'diagnostic') {
|
|
3586
|
+
if (value === undefined || value === null) return [];
|
|
3587
|
+
const diagnostics = Array.isArray(value) ? value : [value];
|
|
3588
|
+
return diagnostics.map((diagnostic, index) => {
|
|
3589
|
+
const normalized = typeof diagnostic === 'string' ? { message: diagnostic } : diagnostic ?? {};
|
|
3590
|
+
const severity = normalizeDiagnosticSeverity(normalized.severity);
|
|
3591
|
+
return Object.freeze({
|
|
3592
|
+
id: normalized.id ?? `diagnostic_${idFragment(adapter.id)}_${idFragment(scope)}_${index + 1}`,
|
|
3593
|
+
severity,
|
|
3594
|
+
code: normalized.code,
|
|
1105
3595
|
phase: normalized.phase ?? 'parse',
|
|
1106
3596
|
kind: normalized.kind,
|
|
1107
3597
|
message: String(normalized.message ?? `${adapter.id} reported a ${severity} diagnostic.`),
|
|
@@ -1201,6 +3691,336 @@ function serializableDiagnostic(diagnostic) {
|
|
|
1201
3691
|
};
|
|
1202
3692
|
}
|
|
1203
3693
|
|
|
3694
|
+
function missingInjectedParserResult(input, details) {
|
|
3695
|
+
const rootId = `native_${idFragment(details.adapterId ?? details.parser)}_missing_parser`;
|
|
3696
|
+
const diagnostic = {
|
|
3697
|
+
severity: 'error',
|
|
3698
|
+
code: 'adapter.parser.missing',
|
|
3699
|
+
phase: 'parse',
|
|
3700
|
+
kind: 'unsupportedSyntax',
|
|
3701
|
+
message: details.message,
|
|
3702
|
+
path: input.sourcePath,
|
|
3703
|
+
metadata: {
|
|
3704
|
+
adapterId: details.adapterId,
|
|
3705
|
+
parser: details.parser
|
|
3706
|
+
}
|
|
3707
|
+
};
|
|
3708
|
+
return {
|
|
3709
|
+
rootId,
|
|
3710
|
+
nodes: {
|
|
3711
|
+
[rootId]: {
|
|
3712
|
+
id: rootId,
|
|
3713
|
+
kind: 'MissingInjectedParser',
|
|
3714
|
+
languageKind: `${input.language}.missingInjectedParser`,
|
|
3715
|
+
value: details.parser,
|
|
3716
|
+
metadata: {
|
|
3717
|
+
adapterId: details.adapterId,
|
|
3718
|
+
parser: details.parser,
|
|
3719
|
+
reason: 'missing-injected-parser'
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
},
|
|
3723
|
+
diagnostics: [diagnostic],
|
|
3724
|
+
losses: [{
|
|
3725
|
+
id: `loss_${idFragment(rootId)}`,
|
|
3726
|
+
severity: 'error',
|
|
3727
|
+
phase: 'parse',
|
|
3728
|
+
sourceFormat: input.language,
|
|
3729
|
+
kind: 'unsupportedSyntax',
|
|
3730
|
+
message: details.message,
|
|
3731
|
+
nodeId: rootId,
|
|
3732
|
+
metadata: {
|
|
3733
|
+
adapterId: details.adapterId,
|
|
3734
|
+
parser: details.parser
|
|
3735
|
+
}
|
|
3736
|
+
}],
|
|
3737
|
+
metadata: {
|
|
3738
|
+
parser: details.parser,
|
|
3739
|
+
adapterId: details.adapterId,
|
|
3740
|
+
missingInjectedParser: true
|
|
3741
|
+
}
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3745
|
+
function normalizeParserErrors(errors, input, options) {
|
|
3746
|
+
return (errors ?? []).map((error, index) => ({
|
|
3747
|
+
id: `diagnostic_${idFragment(options.parser)}_parser_error_${index + 1}`,
|
|
3748
|
+
severity: 'error',
|
|
3749
|
+
code: error.code ?? error.reasonCode,
|
|
3750
|
+
phase: 'parse',
|
|
3751
|
+
kind: 'unsupportedSyntax',
|
|
3752
|
+
message: String(error.message ?? 'Parser reported a syntax error.'),
|
|
3753
|
+
path: input.sourcePath,
|
|
3754
|
+
span: spanFromLoc(error.loc ? { start: error.loc, end: error.loc } : undefined, input),
|
|
3755
|
+
metadata: {
|
|
3756
|
+
parser: options.parser,
|
|
3757
|
+
reasonCode: error.reasonCode
|
|
3758
|
+
}
|
|
3759
|
+
}));
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
function nativeNodeId(context, kind, loc, propertyPath) {
|
|
3763
|
+
context.counter += 1;
|
|
3764
|
+
const start = loc?.start;
|
|
3765
|
+
const line = start?.line ?? 'x';
|
|
3766
|
+
const column = start?.column ?? 'x';
|
|
3767
|
+
return `native_${idFragment(kind)}_${idFragment(line)}_${idFragment(column)}_${context.counter}_${idFragment(propertyPath)}`;
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
function isSyntaxAstNode(value) {
|
|
3771
|
+
return Boolean(value && typeof value === 'object' && typeof (value.type ?? value.kind) === 'string');
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
function ignoredSyntaxField(key) {
|
|
3775
|
+
return key === 'type'
|
|
3776
|
+
|| key === 'kind'
|
|
3777
|
+
|| key === 'loc'
|
|
3778
|
+
|| key === 'start'
|
|
3779
|
+
|| key === 'end'
|
|
3780
|
+
|| key === 'range'
|
|
3781
|
+
|| key === 'comments'
|
|
3782
|
+
|| key === 'leadingComments'
|
|
3783
|
+
|| key === 'trailingComments'
|
|
3784
|
+
|| key === 'innerComments'
|
|
3785
|
+
|| key === 'tokens'
|
|
3786
|
+
|| key === 'extra'
|
|
3787
|
+
|| key === 'parent';
|
|
3788
|
+
}
|
|
3789
|
+
|
|
3790
|
+
function primitiveSyntaxFields(node) {
|
|
3791
|
+
const fields = {};
|
|
3792
|
+
for (const key of ['name', 'operator', 'sourceType', 'async', 'generator', 'computed', 'static', 'exportKind', 'importKind', 'optional']) {
|
|
3793
|
+
if (typeof node[key] === 'string' || typeof node[key] === 'number' || typeof node[key] === 'boolean' || node[key] === null) {
|
|
3794
|
+
fields[key] = node[key];
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
const literal = literalSyntaxValue(node);
|
|
3798
|
+
if (literal !== undefined) fields.literal = literal;
|
|
3799
|
+
if (node.source && typeof node.source === 'object' && typeof node.source.value === 'string') fields.source = node.source.value;
|
|
3800
|
+
return fields;
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3803
|
+
function literalSyntaxValue(node) {
|
|
3804
|
+
if (node.value === null || typeof node.value === 'string' || typeof node.value === 'number' || typeof node.value === 'boolean') return node.value;
|
|
3805
|
+
return undefined;
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
function spanFromLoc(loc, input) {
|
|
3809
|
+
if (!loc?.start) return undefined;
|
|
3810
|
+
return {
|
|
3811
|
+
sourceId: input.sourceHash,
|
|
3812
|
+
path: input.sourcePath ?? loc.filename,
|
|
3813
|
+
startLine: loc.start.line,
|
|
3814
|
+
startColumn: typeof loc.start.column === 'number' ? loc.start.column + 1 : undefined,
|
|
3815
|
+
endLine: loc.end?.line,
|
|
3816
|
+
endColumn: typeof loc.end?.column === 'number' ? loc.end.column + 1 : undefined
|
|
3817
|
+
};
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
function syntaxDeclaration(node, nativeNodeId, input) {
|
|
3821
|
+
const kind = String(node.type ?? node.kind ?? '');
|
|
3822
|
+
if (kind === 'ImportDeclaration') {
|
|
3823
|
+
const name = node.source?.value;
|
|
3824
|
+
if (typeof name === 'string') return declarationRecord(input, nativeNodeId, name, 'module', 'import');
|
|
3825
|
+
}
|
|
3826
|
+
if (kind === 'ExportNamedDeclaration' || kind === 'ExportAllDeclaration') {
|
|
3827
|
+
const name = node.source?.value;
|
|
3828
|
+
if (typeof name === 'string') return declarationRecord(input, nativeNodeId, name, 'module', 'export');
|
|
3829
|
+
}
|
|
3830
|
+
if (kind === 'FunctionDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'function');
|
|
3831
|
+
if (kind === 'ClassDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'class');
|
|
3832
|
+
if (kind === 'TSInterfaceDeclaration' || kind === 'InterfaceDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'interface');
|
|
3833
|
+
if (kind === 'TSTypeAliasDeclaration' || kind === 'TypeAliasDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'type');
|
|
3834
|
+
if (kind === 'VariableDeclarator') return namedDeclaration(input, nativeNodeId, node.id, 'variable');
|
|
3835
|
+
return undefined;
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
function typeScriptDeclaration(node, kind, nativeNodeId, input) {
|
|
3839
|
+
if (kind === 'ImportDeclaration' || kind === 'ImportEqualsDeclaration') {
|
|
3840
|
+
const name = stringFromTsExpression(node.moduleSpecifier) ?? stringFromTsExpression(node.externalModuleReference?.expression);
|
|
3841
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
|
|
3842
|
+
}
|
|
3843
|
+
if (kind === 'FunctionDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'function');
|
|
3844
|
+
if (kind === 'ClassDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'class');
|
|
3845
|
+
if (kind === 'InterfaceDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'interface');
|
|
3846
|
+
if (kind === 'TypeAliasDeclaration' || kind === 'EnumDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'type');
|
|
3847
|
+
if (kind === 'VariableDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'variable');
|
|
3848
|
+
if (kind === 'MethodDeclaration' || kind === 'MethodSignature') return namedDeclaration(input, nativeNodeId, node.name, 'method');
|
|
3849
|
+
return undefined;
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
function treeSitterDeclaration(node, kind, nativeNodeId, input) {
|
|
3853
|
+
if (/import|include|use/.test(kind)) {
|
|
3854
|
+
const name = treeSitterFieldText(node, 'path') ?? treeSitterFieldText(node, 'source') ?? shortNodeText(node);
|
|
3855
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
|
|
3856
|
+
}
|
|
3857
|
+
if (/function|method|fn_item|function_declaration/.test(kind)) {
|
|
3858
|
+
const name = treeSitterFieldText(node, 'name');
|
|
3859
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'function', 'definition');
|
|
3860
|
+
}
|
|
3861
|
+
if (/class/.test(kind)) {
|
|
3862
|
+
const name = treeSitterFieldText(node, 'name');
|
|
3863
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'class', 'definition');
|
|
3864
|
+
}
|
|
3865
|
+
if (/interface/.test(kind)) {
|
|
3866
|
+
const name = treeSitterFieldText(node, 'name');
|
|
3867
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'interface', 'definition');
|
|
3868
|
+
}
|
|
3869
|
+
if (/struct|enum|type/.test(kind)) {
|
|
3870
|
+
const name = treeSitterFieldText(node, 'name');
|
|
3871
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'type', 'definition');
|
|
3872
|
+
}
|
|
3873
|
+
return undefined;
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
function namedDeclaration(input, nativeNodeId, nameNode, symbolKind) {
|
|
3877
|
+
const name = identifierName(nameNode);
|
|
3878
|
+
return name ? declarationRecord(input, nativeNodeId, name, symbolKind, 'definition') : undefined;
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
function declarationRecord(input, nativeNodeId, name, symbolKind, role = 'definition') {
|
|
3882
|
+
return {
|
|
3883
|
+
name: String(name),
|
|
3884
|
+
symbolKind,
|
|
3885
|
+
role,
|
|
3886
|
+
symbolId: `symbol:${input.language}:${role === 'import' ? 'import:' : ''}${idFragment(name)}`,
|
|
3887
|
+
nativeNodeId
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
function relationPredicateForDeclaration(declaration) {
|
|
3892
|
+
if (declaration.role === 'import') return 'imports';
|
|
3893
|
+
if (declaration.role === 'export') return 'exports';
|
|
3894
|
+
return 'defines';
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3897
|
+
function identifierName(node) {
|
|
3898
|
+
if (!node) return undefined;
|
|
3899
|
+
if (typeof node === 'string') return node;
|
|
3900
|
+
if (typeof node.name === 'string') return node.name;
|
|
3901
|
+
if (typeof node.escapedText === 'string') return node.escapedText;
|
|
3902
|
+
if (typeof node.text === 'string') return node.text;
|
|
3903
|
+
if (node.type === 'Identifier' && typeof node.value === 'string') return node.value;
|
|
3904
|
+
return undefined;
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
function stringFromTsExpression(node) {
|
|
3908
|
+
if (!node) return undefined;
|
|
3909
|
+
if (typeof node.text === 'string') return node.text;
|
|
3910
|
+
if (typeof node.value === 'string') return node.value;
|
|
3911
|
+
return identifierName(node);
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
function typeScriptKindName(node, ts) {
|
|
3915
|
+
if (typeof node.kindName === 'string') return node.kindName;
|
|
3916
|
+
if (ts?.SyntaxKind && node.kind !== undefined) return ts.SyntaxKind[node.kind] ?? `SyntaxKind${node.kind}`;
|
|
3917
|
+
if (typeof node.kind === 'string') return node.kind;
|
|
3918
|
+
return `SyntaxKind${node.kind ?? 'Unknown'}`;
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3921
|
+
function spanFromTypeScriptNode(node, sourceFile) {
|
|
3922
|
+
const start = typeof node.getStart === 'function' ? node.getStart(sourceFile) : node.pos;
|
|
3923
|
+
const end = typeof node.getEnd === 'function' ? node.getEnd() : node.end;
|
|
3924
|
+
if (typeof start !== 'number' || typeof sourceFile?.getLineAndCharacterOfPosition !== 'function') return undefined;
|
|
3925
|
+
const startPos = sourceFile.getLineAndCharacterOfPosition(start);
|
|
3926
|
+
const endPos = typeof end === 'number' ? sourceFile.getLineAndCharacterOfPosition(end) : undefined;
|
|
3927
|
+
return {
|
|
3928
|
+
sourceId: sourceFile.sourceHash,
|
|
3929
|
+
path: sourceFile.fileName,
|
|
3930
|
+
startLine: startPos.line + 1,
|
|
3931
|
+
startColumn: startPos.character + 1,
|
|
3932
|
+
endLine: endPos ? endPos.line + 1 : undefined,
|
|
3933
|
+
endColumn: endPos ? endPos.character + 1 : undefined
|
|
3934
|
+
};
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3937
|
+
function typeScriptNodeValue(node) {
|
|
3938
|
+
return identifierName(node.name) ?? stringFromTsExpression(node.moduleSpecifier) ?? undefined;
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
function primitiveTypeScriptFields(node, kind) {
|
|
3942
|
+
const fields = { kind };
|
|
3943
|
+
const name = identifierName(node.name);
|
|
3944
|
+
if (name) fields.name = name;
|
|
3945
|
+
const moduleSpecifier = stringFromTsExpression(node.moduleSpecifier);
|
|
3946
|
+
if (moduleSpecifier) fields.moduleSpecifier = moduleSpecifier;
|
|
3947
|
+
return fields;
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3950
|
+
function spanFromTreeSitterNode(node, input) {
|
|
3951
|
+
const start = node.startPosition;
|
|
3952
|
+
if (!start) return undefined;
|
|
3953
|
+
const end = node.endPosition;
|
|
3954
|
+
return {
|
|
3955
|
+
sourceId: input.sourceHash,
|
|
3956
|
+
path: input.sourcePath,
|
|
3957
|
+
startLine: start.row + 1,
|
|
3958
|
+
startColumn: start.column + 1,
|
|
3959
|
+
endLine: end ? end.row + 1 : undefined,
|
|
3960
|
+
endColumn: end ? end.column + 1 : undefined
|
|
3961
|
+
};
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
function treeSitterFieldText(node, field) {
|
|
3965
|
+
if (typeof node.childForFieldName !== 'function') return undefined;
|
|
3966
|
+
return shortNodeText(node.childForFieldName(field));
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
function shortNodeText(node) {
|
|
3970
|
+
if (!node || typeof node.text !== 'string') return undefined;
|
|
3971
|
+
const text = node.text.trim();
|
|
3972
|
+
if (!text || text.length > 160) return undefined;
|
|
3973
|
+
return text.replace(/^['"]|['"]$/g, '');
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3976
|
+
function truncatedAstLoss(input, context, options) {
|
|
3977
|
+
return {
|
|
3978
|
+
id: `loss_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}_truncated`,
|
|
3979
|
+
severity: 'warning',
|
|
3980
|
+
phase: 'read',
|
|
3981
|
+
sourceFormat: input.language,
|
|
3982
|
+
kind: 'opaqueNative',
|
|
3983
|
+
message: `Native AST normalization stopped after ${context.maxNodes} node(s).`,
|
|
3984
|
+
metadata: {
|
|
3985
|
+
parser: options.parser,
|
|
3986
|
+
astFormat: options.astFormat,
|
|
3987
|
+
maxNodes: context.maxNodes
|
|
3988
|
+
}
|
|
3989
|
+
};
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
function uniqueStrings(values) {
|
|
3993
|
+
return [...new Set((values ?? []).map((value) => String(value)).filter(Boolean))];
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
function uniqueByLossId(values) {
|
|
3997
|
+
const seen = new Set();
|
|
3998
|
+
const result = [];
|
|
3999
|
+
for (const value of values ?? []) {
|
|
4000
|
+
const id = value?.id ?? `loss_${result.length + 1}`;
|
|
4001
|
+
if (seen.has(id)) continue;
|
|
4002
|
+
seen.add(id);
|
|
4003
|
+
result.push(value.id ? value : { ...value, id });
|
|
4004
|
+
}
|
|
4005
|
+
return result;
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
function uniqueByEvidenceId(values) {
|
|
4009
|
+
const seen = new Set();
|
|
4010
|
+
const result = [];
|
|
4011
|
+
for (const value of values ?? []) {
|
|
4012
|
+
const id = value?.id ?? `evidence_${result.length + 1}`;
|
|
4013
|
+
if (seen.has(id)) continue;
|
|
4014
|
+
seen.add(id);
|
|
4015
|
+
result.push(value.id ? value : { ...value, id });
|
|
4016
|
+
}
|
|
4017
|
+
return result;
|
|
4018
|
+
}
|
|
4019
|
+
|
|
4020
|
+
function numberOrUndefined(value) {
|
|
4021
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
4022
|
+
}
|
|
4023
|
+
|
|
1204
4024
|
function idFragment(value) {
|
|
1205
4025
|
return String(value ?? 'native')
|
|
1206
4026
|
.toLowerCase()
|