@shapeshift-labs/frontier-lang-compiler 0.2.53 → 0.2.55
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 +13 -0
- package/bench/smoke.mjs +1 -0
- package/bench/source-change-suite.mjs +3 -1
- package/dist/declarations/semantic-sidecar.d.ts +12 -10
- package/dist/internal/index-impl/createLightweightNativeImport.js +10 -3
- package/dist/internal/index-impl/createSemanticImportSidecar.js +10 -2
- package/dist/internal/index-impl/treeSitterDeclaration.js +14 -6
- package/dist/internal/index-impl/treeSitterFieldText.js +1 -5
- package/dist/internal/index-impl/treeSitterNodeAccess.js +75 -0
- package/dist/internal/index-impl/visitTreeSitterNode.js +2 -28
- package/dist/lightweight-dependency-relations.js +271 -0
- package/dist/semantic-import-dependencies.js +62 -0
- package/dist/semantic-import-sidecar-entry.js +17 -2
- package/dist/semantic-import-sidecar-types.d.ts +22 -0
- package/examples/native-js-to-rust-demo.mjs +148 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,19 @@ const result = compileFrontierSource(source, { target: 'typescript' });
|
|
|
19
19
|
if (result.ok) console.log(result.output);
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
Run a small end-to-end demo after installing or building the package:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm run build
|
|
26
|
+
node examples/native-js-to-rust-demo.mjs
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The demo prints JavaScript source, the Frontier universal AST/semantic-index summary,
|
|
30
|
+
Rust declaration stubs, a host-adapter Rust projection, and a direct Frontier-source
|
|
31
|
+
to Rust projection. Native JavaScript projection remains loss-aware: without a
|
|
32
|
+
target adapter the compiler emits review-required stubs rather than claiming a
|
|
33
|
+
lossless JS-to-Rust transpilation.
|
|
34
|
+
|
|
22
35
|
Emit code with declaration-level source-map sidecars for semantic review and merge admission:
|
|
23
36
|
|
|
24
37
|
```js
|
package/bench/smoke.mjs
CHANGED
|
@@ -97,6 +97,7 @@ console.log(JSON.stringify({
|
|
|
97
97
|
nativeTargetAdapterDurationMs: Number(transformMetrics.nativeTargetAdapterDurationMs.toFixed(2)),
|
|
98
98
|
regionScanImports: sourceChangeMetrics.regionScanImports,
|
|
99
99
|
regionScanSymbols: sourceChangeMetrics.regionScanSymbols,
|
|
100
|
+
regionScanDependencyRelations: sourceChangeMetrics.regionScanDependencyRelations,
|
|
100
101
|
regionScanOwnershipRegions: sourceChangeMetrics.regionScanOwnershipRegions,
|
|
101
102
|
regionScanDurationMs: Number(sourceChangeMetrics.regionScanDurationMs.toFixed(2)),
|
|
102
103
|
changeProjectionSets: sourceChangeMetrics.changeProjectionSets,
|
|
@@ -22,6 +22,7 @@ function measureRegionScan() {
|
|
|
22
22
|
language: 'typescript',
|
|
23
23
|
sourcePath: `src/regions-${index}.ts`,
|
|
24
24
|
sourceText: `
|
|
25
|
+
export function normalizeCount${index}(value) { return value; }
|
|
25
26
|
export const appRoutes${index} = [
|
|
26
27
|
{ path: "/${index}", component: Screen${index} },
|
|
27
28
|
{ path: "/${index}/settings", component: Settings${index} }
|
|
@@ -31,7 +32,7 @@ function measureRegionScan() {
|
|
|
31
32
|
legal: { title: "Legal ${index}" }
|
|
32
33
|
};
|
|
33
34
|
export const runtimeConfig${index} = {
|
|
34
|
-
limits: { count: ${index} },
|
|
35
|
+
limits: { count: normalizeCount${index}(${index}) },
|
|
35
36
|
resolve(id) { return id; }
|
|
36
37
|
};
|
|
37
38
|
export const helpers${index} = {
|
|
@@ -45,6 +46,7 @@ function measureRegionScan() {
|
|
|
45
46
|
return {
|
|
46
47
|
regionScanImports: regionScanImports.length,
|
|
47
48
|
regionScanSymbols: regionScanImports.reduce((sum, entry) => sum + entry.imported.semanticIndex.symbols.length, 0),
|
|
49
|
+
regionScanDependencyRelations: regionScanImports.reduce((sum, entry) => sum + entry.sidecar.dependencies.total, 0),
|
|
48
50
|
regionScanOwnershipRegions: regionScanImports.reduce((sum, entry) => sum + entry.sidecar.ownershipRegions.length, 0),
|
|
49
51
|
regionScanDurationMs
|
|
50
52
|
};
|
|
@@ -132,6 +132,7 @@ export interface SemanticImportSidecarImportEntry {
|
|
|
132
132
|
readonly universalAstLayerIds: readonly string[];
|
|
133
133
|
readonly proofSpec: SemanticImportSidecarProofSpecSummary;
|
|
134
134
|
readonly paradigmSemantics: SemanticImportSidecarParadigmSemanticsSummary;
|
|
135
|
+
readonly dependencyRelationCount: number; readonly dependencyPredicates: readonly string[];
|
|
135
136
|
readonly readiness: SemanticMergeReadiness;
|
|
136
137
|
readonly emptySemanticIndex: boolean;
|
|
137
138
|
readonly regionTaxonomy?: SemanticImportRegionTaxonomySummary;
|
|
@@ -228,6 +229,13 @@ export interface SemanticImportSidecarParadigmSemanticsSummary {
|
|
|
228
229
|
readonly empty: boolean;
|
|
229
230
|
}
|
|
230
231
|
|
|
232
|
+
export interface SemanticImportDependencySummary {
|
|
233
|
+
readonly total: number; readonly calls: number; readonly uses: number; readonly references: number;
|
|
234
|
+
readonly imports: number; readonly extends: number; readonly implements: number; readonly includes: number; readonly requires: number;
|
|
235
|
+
readonly byPredicate: Readonly<Record<string, number>>; readonly predicates: readonly string[];
|
|
236
|
+
readonly ids: readonly string[]; readonly sourceSymbolIds: readonly string[]; readonly targetSymbolIds: readonly string[];
|
|
237
|
+
}
|
|
238
|
+
|
|
231
239
|
export interface SemanticImportSidecar {
|
|
232
240
|
readonly kind: 'frontier.lang.semanticImportSidecar';
|
|
233
241
|
readonly version: 1;
|
|
@@ -238,11 +246,7 @@ export interface SemanticImportSidecar {
|
|
|
238
246
|
readonly imports: readonly SemanticImportSidecarImportEntry[];
|
|
239
247
|
readonly symbols: readonly SemanticImportSidecarSymbol[];
|
|
240
248
|
readonly ownershipRegions: readonly SemanticImportOwnershipRegion[];
|
|
241
|
-
readonly sourceMaps: {
|
|
242
|
-
readonly total: number;
|
|
243
|
-
readonly mappings: number;
|
|
244
|
-
readonly ids: readonly string[];
|
|
245
|
-
};
|
|
249
|
+
readonly sourceMaps: { readonly total: number; readonly mappings: number; readonly ids: readonly string[] };
|
|
246
250
|
readonly sourcePreservation: {
|
|
247
251
|
readonly total: number;
|
|
248
252
|
readonly ids: readonly string[];
|
|
@@ -259,6 +263,7 @@ export interface SemanticImportSidecar {
|
|
|
259
263
|
readonly universalAstLayers: SemanticImportSidecarUniversalAstLayerSummary;
|
|
260
264
|
readonly proofSpec: SemanticImportSidecarProofSpecSummary;
|
|
261
265
|
readonly paradigmSemantics: SemanticImportSidecarParadigmSemanticsSummary;
|
|
266
|
+
readonly dependencies: SemanticImportDependencySummary;
|
|
262
267
|
readonly patchHints: readonly SemanticImportPatchHint[];
|
|
263
268
|
readonly quality: SemanticImportSidecarQuality;
|
|
264
269
|
readonly admission: SemanticImportSidecarAdmission;
|
|
@@ -280,11 +285,7 @@ export interface SemanticImportSidecar {
|
|
|
280
285
|
readonly reviewLossIds: readonly string[];
|
|
281
286
|
};
|
|
282
287
|
readonly regionTaxonomy: SemanticImportRegionTaxonomySummary;
|
|
283
|
-
readonly evidence: {
|
|
284
|
-
readonly total: number;
|
|
285
|
-
readonly failed: readonly string[];
|
|
286
|
-
readonly ids: readonly string[];
|
|
287
|
-
};
|
|
288
|
+
readonly evidence: { readonly total: number; readonly failed: readonly string[]; readonly ids: readonly string[] };
|
|
288
289
|
readonly summary: {
|
|
289
290
|
readonly imports: number;
|
|
290
291
|
readonly symbols: number;
|
|
@@ -300,6 +301,7 @@ export interface SemanticImportSidecar {
|
|
|
300
301
|
readonly paradigmSemanticsRecords: number;
|
|
301
302
|
readonly paradigmSemanticsGroups: number;
|
|
302
303
|
readonly paradigmSemanticsLoweringRecords: number;
|
|
304
|
+
readonly dependencyRelations: number; readonly dependencyPredicates: readonly string[];
|
|
303
305
|
readonly patchHints: number;
|
|
304
306
|
readonly evidenceWarnings: number;
|
|
305
307
|
readonly readiness: SemanticMergeReadiness;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{idFragment}from'../../native-import-utils.js';import{lightweightCoverageLosses,scanNativeDeclarations}from'../../native-region-scanner.js';import{semanticOwnershipRegionForDeclaration}from'../../semantic-import-regions.js';import{createSemanticIndexRecord,hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
|
|
1
|
+
import{idFragment}from'../../native-import-utils.js';import{lightweightDependencyRelations}from'../../lightweight-dependency-relations.js';import{lightweightCoverageLosses,scanNativeDeclarations}from'../../native-region-scanner.js';import{semanticOwnershipRegionForDeclaration}from'../../semantic-import-regions.js';import{createSemanticIndexRecord,hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
|
|
2
2
|
export function createLightweightNativeImport(input) {
|
|
3
3
|
const parser = input.parser ?? `${input.language}.lightweight-declaration-scan`;
|
|
4
4
|
const rootId = 'native_root';
|
|
@@ -20,6 +20,7 @@ export function createLightweightNativeImport(input) {
|
|
|
20
20
|
const facts = [];
|
|
21
21
|
const mappings = [];
|
|
22
22
|
const evidenceId = `evidence_${idFragment(input.sourcePath ?? input.language)}_lightweight_scan`;
|
|
23
|
+
const dependencies = lightweightDependencyRelations(input, declarations, documentId);
|
|
23
24
|
|
|
24
25
|
for (const declaration of declarations) {
|
|
25
26
|
const ownershipRegion = semanticOwnershipRegionForDeclaration(input, declaration, documentId);
|
|
@@ -105,6 +106,9 @@ export function createLightweightNativeImport(input) {
|
|
|
105
106
|
}
|
|
106
107
|
if (declaration.loss) losses.push(declaration.loss);
|
|
107
108
|
}
|
|
109
|
+
occurrences.push(...dependencies.occurrences);
|
|
110
|
+
relations.push(...dependencies.relations);
|
|
111
|
+
facts.push(...dependencies.facts);
|
|
108
112
|
losses.push(...lightweightCoverageLosses(input, declarations, input.sourcePreservation));
|
|
109
113
|
|
|
110
114
|
const semanticIndex = createSemanticIndexRecord({
|
|
@@ -124,12 +128,13 @@ export function createLightweightNativeImport(input) {
|
|
|
124
128
|
kind: 'import',
|
|
125
129
|
status: 'passed',
|
|
126
130
|
path: input.sourcePath,
|
|
127
|
-
summary: `Lightweight declaration scan found ${symbols.length} symbol(s).`,
|
|
128
|
-
metadata: { parser }
|
|
131
|
+
summary: `Lightweight declaration scan found ${symbols.length} symbol(s) and ${dependencies.summary.total} dependency edge(s).`,
|
|
132
|
+
metadata: { parser, dependencyRelations: dependencies.summary.total }
|
|
129
133
|
}],
|
|
130
134
|
metadata: {
|
|
131
135
|
parser,
|
|
132
136
|
coverage: 'declarations-only',
|
|
137
|
+
dependencyRelations: dependencies.summary,
|
|
133
138
|
unsupported: ['full expression AST', 'type checking', 'control flow', 'comments and formatting preservation']
|
|
134
139
|
}
|
|
135
140
|
});
|
|
@@ -145,6 +150,8 @@ export function createLightweightNativeImport(input) {
|
|
|
145
150
|
parser,
|
|
146
151
|
scanKind: 'lightweight-declaration-scan',
|
|
147
152
|
declarationCount: declarations.length,
|
|
153
|
+
dependencyRelationCount: dependencies.summary.total,
|
|
154
|
+
dependencyOccurrenceCount: dependencies.occurrences.length,
|
|
148
155
|
...(input.sourcePreservation ? {
|
|
149
156
|
sourcePreservationId: input.sourcePreservation.id,
|
|
150
157
|
sourcePreservationSummary: input.sourcePreservation.summary
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{idFragment,maxSemanticMergeReadiness,uniqueRecordsById}from'../../native-import-utils.js';import{summarizeSemanticImportSidecarParadigmSemantics,summarizeSemanticImportSidecarProofSpec,summarizeSemanticImportSidecarUniversalAstLayers}from'../../semantic-import-layers.js';import{semanticPatchHintForRegion,summarizeSemanticImportRegionTaxonomy}from'../../semantic-import-regions.js';import{semanticImportSidecarEntry}from'../../semantic-import-sidecar-entry.js';import{summarizeKernelSourcePreservation}from'../../semantic-import-source-preservation.js';
|
|
1
|
+
import{idFragment,maxSemanticMergeReadiness,uniqueRecordsById}from'../../native-import-utils.js';import{summarizeSemanticImportDependencies}from'../../semantic-import-dependencies.js';import{summarizeSemanticImportSidecarParadigmSemantics,summarizeSemanticImportSidecarProofSpec,summarizeSemanticImportSidecarUniversalAstLayers}from'../../semantic-import-layers.js';import{semanticPatchHintForRegion,summarizeSemanticImportRegionTaxonomy}from'../../semantic-import-regions.js';import{semanticImportSidecarEntry}from'../../semantic-import-sidecar-entry.js';import{summarizeKernelSourcePreservation}from'../../semantic-import-source-preservation.js';
|
|
2
2
|
import{createSemanticImportSidecarAdmission,createSemanticImportSidecarQuality}from'./createSemanticImportSidecarAdmission.js';
|
|
3
3
|
import{summarizeNativeImportLosses}from'./summarizeNativeImportLosses.js';
|
|
4
4
|
export function createSemanticImportSidecar(importResult, options = {}) {
|
|
@@ -17,9 +17,14 @@ export function createSemanticImportSidecar(importResult, options = {}) {
|
|
|
17
17
|
const universalAstLayers = summarizeSemanticImportSidecarUniversalAstLayers(importEntries);
|
|
18
18
|
const proofSpec = summarizeSemanticImportSidecarProofSpec(importEntries);
|
|
19
19
|
const paradigmSemantics = summarizeSemanticImportSidecarParadigmSemantics(importEntries);
|
|
20
|
+
const dependencies = summarizeSemanticImportDependencies(imports);
|
|
21
|
+
const entryReadiness = importEntries.reduce(
|
|
22
|
+
(current, entry) => maxSemanticMergeReadiness(current, entry.readiness),
|
|
23
|
+
lossSummary.semanticMergeReadiness
|
|
24
|
+
);
|
|
20
25
|
const readiness = mergeCandidates.reduce(
|
|
21
26
|
(current, candidate) => maxSemanticMergeReadiness(current, candidate.readiness),
|
|
22
|
-
|
|
27
|
+
entryReadiness
|
|
23
28
|
);
|
|
24
29
|
const patchHints = ownershipRegions.map((region) => semanticPatchHintForRegion(region, readiness, options));
|
|
25
30
|
const quality = createSemanticImportSidecarQuality({
|
|
@@ -51,6 +56,7 @@ export function createSemanticImportSidecar(importResult, options = {}) {
|
|
|
51
56
|
universalAstLayers,
|
|
52
57
|
proofSpec,
|
|
53
58
|
paradigmSemantics,
|
|
59
|
+
dependencies,
|
|
54
60
|
patchHints,
|
|
55
61
|
quality,
|
|
56
62
|
admission,
|
|
@@ -92,6 +98,8 @@ export function createSemanticImportSidecar(importResult, options = {}) {
|
|
|
92
98
|
paradigmSemanticsRecords: paradigmSemantics.total,
|
|
93
99
|
paradigmSemanticsGroups: paradigmSemantics.groups.length,
|
|
94
100
|
paradigmSemanticsLoweringRecords: paradigmSemantics.loweringRecords,
|
|
101
|
+
dependencyRelations: dependencies.total,
|
|
102
|
+
dependencyPredicates: dependencies.predicates,
|
|
95
103
|
patchHints: patchHints.length,
|
|
96
104
|
evidenceWarnings: quality.emptyEvidenceWarnings.length,
|
|
97
105
|
readiness,
|
|
@@ -1,24 +1,32 @@
|
|
|
1
|
-
import{declarationRecord}from'./declarationRecord.js';import{
|
|
1
|
+
import{declarationRecord}from'./declarationRecord.js';import{shortNodeText}from'./shortNodeText.js';import{treeSitterChildText,treeSitterFieldText}from'./treeSitterNodeAccess.js';
|
|
2
2
|
export function treeSitterDeclaration(node, kind, nativeNodeId, input) {
|
|
3
3
|
if (/import|include|use/.test(kind)) {
|
|
4
|
-
const name =
|
|
4
|
+
const name = treeSitterNamedText(node, ['string', 'string_fragment', 'identifier']) ?? shortNodeText(node);
|
|
5
5
|
if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
|
|
6
6
|
}
|
|
7
7
|
if (/function|method|fn_item|function_declaration/.test(kind)) {
|
|
8
|
-
const name =
|
|
8
|
+
const name = treeSitterNamedText(node, ['identifier', 'property_identifier']);
|
|
9
9
|
if (name) return declarationRecord(input, nativeNodeId, name, 'function', 'definition');
|
|
10
10
|
}
|
|
11
11
|
if (/class/.test(kind)) {
|
|
12
|
-
const name =
|
|
12
|
+
const name = treeSitterNamedText(node, ['type_identifier', 'identifier']);
|
|
13
13
|
if (name) return declarationRecord(input, nativeNodeId, name, 'class', 'definition');
|
|
14
14
|
}
|
|
15
15
|
if (/interface/.test(kind)) {
|
|
16
|
-
const name =
|
|
16
|
+
const name = treeSitterNamedText(node, ['type_identifier', 'identifier']);
|
|
17
17
|
if (name) return declarationRecord(input, nativeNodeId, name, 'interface', 'definition');
|
|
18
18
|
}
|
|
19
19
|
if (/struct|enum|type/.test(kind)) {
|
|
20
|
-
const name =
|
|
20
|
+
const name = treeSitterNamedText(node, ['type_identifier', 'identifier']);
|
|
21
21
|
if (name) return declarationRecord(input, nativeNodeId, name, 'type', 'definition');
|
|
22
22
|
}
|
|
23
|
+
if (/variable_declarator|property_declaration|field_declaration/.test(kind)) {
|
|
24
|
+
const name = treeSitterNamedText(node, ['identifier', 'property_identifier', 'field_identifier']);
|
|
25
|
+
if (name) return declarationRecord(input, nativeNodeId, name, 'variable', 'definition');
|
|
26
|
+
}
|
|
23
27
|
return undefined;
|
|
24
28
|
}
|
|
29
|
+
|
|
30
|
+
function treeSitterNamedText(node, childKinds) {
|
|
31
|
+
return treeSitterFieldText(node, 'name') ?? treeSitterFieldText(node, 'declarator') ?? treeSitterChildText(node, childKinds);
|
|
32
|
+
}
|
|
@@ -1,5 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export function treeSitterFieldText(node, field) {
|
|
3
|
-
if (typeof node.childForFieldName !== 'function') return undefined;
|
|
4
|
-
return shortNodeText(node.childForFieldName(field));
|
|
5
|
-
}
|
|
1
|
+
export{treeSitterFieldText}from'./treeSitterNodeAccess.js';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import{shortNodeText}from'./shortNodeText.js';
|
|
2
|
+
export function treeSitterNodeKind(node) {
|
|
3
|
+
return String(node?.type ?? node?.kind ?? 'node');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function treeSitterChildren(node) {
|
|
7
|
+
const children = treeSitterArrayChildren(node, 'children');
|
|
8
|
+
if (children.length) return children;
|
|
9
|
+
const childCountChildren = treeSitterIndexedChildren(node, 'childCount', 'child');
|
|
10
|
+
if (childCountChildren.length) return childCountChildren;
|
|
11
|
+
const namedChildren = treeSitterArrayChildren(node, 'namedChildren');
|
|
12
|
+
if (namedChildren.length) return namedChildren;
|
|
13
|
+
return treeSitterIndexedChildren(node, 'namedChildCount', 'namedChild');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function treeSitterChildText(node, kinds) {
|
|
17
|
+
const wanted = new Set(kinds);
|
|
18
|
+
const stack = [...treeSitterChildren(node)];
|
|
19
|
+
while (stack.length) {
|
|
20
|
+
const child = stack.shift();
|
|
21
|
+
if (!child || typeof child !== 'object') continue;
|
|
22
|
+
if (wanted.has(treeSitterNodeKind(child))) {
|
|
23
|
+
const text = shortNodeText(child);
|
|
24
|
+
if (text) return text;
|
|
25
|
+
}
|
|
26
|
+
stack.unshift(...treeSitterChildren(child));
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function treeSitterFieldText(node, field) {
|
|
32
|
+
const exact = treeSitterFieldNode(node, field);
|
|
33
|
+
if (exact) return shortNodeText(exact);
|
|
34
|
+
return treeSitterNamedFieldText(node, field);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function treeSitterFieldNode(node, field) {
|
|
38
|
+
if (typeof node?.childForFieldName === 'function') return node.childForFieldName(field);
|
|
39
|
+
if (typeof node?.child_by_field_name === 'function') return node.child_by_field_name(field);
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function treeSitterNamedFieldText(node, field) {
|
|
44
|
+
for (const child of treeSitterChildren(node)) {
|
|
45
|
+
if (treeSitterFieldName(child) === field) {
|
|
46
|
+
const text = shortNodeText(child);
|
|
47
|
+
if (text) return text;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function treeSitterFieldName(node) {
|
|
54
|
+
if (typeof node?.fieldName === 'string') return node.fieldName;
|
|
55
|
+
if (typeof node?.field_name === 'string') return node.field_name;
|
|
56
|
+
if (typeof node?.fieldName === 'function') return node.fieldName();
|
|
57
|
+
if (typeof node?.field_name === 'function') return node.field_name();
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function treeSitterArrayChildren(node, field) {
|
|
62
|
+
const value = node?.[field];
|
|
63
|
+
return Array.isArray(value) ? value.filter((child) => child && typeof child === 'object') : [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function treeSitterIndexedChildren(node, countField, childMethod) {
|
|
67
|
+
const count = Number(node?.[countField]);
|
|
68
|
+
if (!Number.isFinite(count) || count <= 0 || typeof node?.[childMethod] !== 'function') return [];
|
|
69
|
+
const children = [];
|
|
70
|
+
for (let index = 0; index < count; index += 1) {
|
|
71
|
+
const child = node[childMethod](index);
|
|
72
|
+
if (child && typeof child === 'object') children.push(child);
|
|
73
|
+
}
|
|
74
|
+
return children;
|
|
75
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{idFragment}from'../../native-import-utils.js';
|
|
2
|
-
import{nativeNodeId}from'./nativeNodeId.js';import{numberOrUndefined}from'./numberOrUndefined.js';import{shortNodeText}from'./shortNodeText.js';import{spanFromTreeSitterNode}from'./spanFromTreeSitterNode.js';import{treeSitterDeclaration}from'./treeSitterDeclaration.js';
|
|
2
|
+
import{nativeNodeId}from'./nativeNodeId.js';import{numberOrUndefined}from'./numberOrUndefined.js';import{shortNodeText}from'./shortNodeText.js';import{spanFromTreeSitterNode}from'./spanFromTreeSitterNode.js';import{treeSitterDeclaration}from'./treeSitterDeclaration.js';import{treeSitterChildren,treeSitterNodeKind}from'./treeSitterNodeAccess.js';
|
|
3
3
|
export function visitTreeSitterNode(node, context, propertyPath, depth = 0) {
|
|
4
4
|
if (!node || typeof node !== 'object' || context.truncated) return undefined;
|
|
5
5
|
if (context.objectIds.has(node)) return context.objectIds.get(node);
|
|
@@ -8,7 +8,7 @@ export function visitTreeSitterNode(node, context, propertyPath, depth = 0) {
|
|
|
8
8
|
return undefined;
|
|
9
9
|
}
|
|
10
10
|
context.counter += 1;
|
|
11
|
-
const kind =
|
|
11
|
+
const kind = treeSitterNodeKind(node);
|
|
12
12
|
const span = spanFromTreeSitterNode(node, context.input);
|
|
13
13
|
const id = nativeNodeId(context, kind, { start: { line: span?.startLine, column: span?.startColumn } }, propertyPath);
|
|
14
14
|
context.objectIds.set(node, id);
|
|
@@ -82,32 +82,6 @@ export function visitTreeSitterNode(node, context, propertyPath, depth = 0) {
|
|
|
82
82
|
return id;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
function treeSitterChildren(node) {
|
|
86
|
-
const children = treeSitterArrayChildren(node, 'children');
|
|
87
|
-
if (children.length) return children;
|
|
88
|
-
const childCountChildren = treeSitterIndexedChildren(node, 'childCount', 'child');
|
|
89
|
-
if (childCountChildren.length) return childCountChildren;
|
|
90
|
-
const namedChildren = treeSitterArrayChildren(node, 'namedChildren');
|
|
91
|
-
if (namedChildren.length) return namedChildren;
|
|
92
|
-
return treeSitterIndexedChildren(node, 'namedChildCount', 'namedChild');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function treeSitterArrayChildren(node, field) {
|
|
96
|
-
const value = node[field];
|
|
97
|
-
return Array.isArray(value) ? value.filter((child) => child && typeof child === 'object') : [];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function treeSitterIndexedChildren(node, countField, childMethod) {
|
|
101
|
-
const count = Number(node[countField]);
|
|
102
|
-
if (!Number.isFinite(count) || count <= 0 || typeof node[childMethod] !== 'function') return [];
|
|
103
|
-
const children = [];
|
|
104
|
-
for (let index = 0; index < count; index += 1) {
|
|
105
|
-
const child = node[childMethod](index);
|
|
106
|
-
if (child && typeof child === 'object') children.push(child);
|
|
107
|
-
}
|
|
108
|
-
return children;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
85
|
function treeSitterBoolean(node, ...fields) {
|
|
112
86
|
for (const field of fields) {
|
|
113
87
|
const value = node[field];
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { idFragment } from './native-import-utils.js';
|
|
2
|
+
import { sourceLines } from './native-region-scanner-core.js';
|
|
3
|
+
|
|
4
|
+
const jsLikeLanguages = new Set(['javascript', 'typescript']);
|
|
5
|
+
const ignoredIdentifiers = new Set([
|
|
6
|
+
'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'default',
|
|
7
|
+
'delete', 'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'from',
|
|
8
|
+
'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'null', 'of',
|
|
9
|
+
'return', 'static', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof',
|
|
10
|
+
'undefined', 'var', 'void', 'while', 'with', 'yield'
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export function lightweightDependencyRelations(input, declarations, documentId) {
|
|
14
|
+
if (!jsLikeLanguages.has(String(input.language ?? '').toLowerCase())) {
|
|
15
|
+
return { relations: [], occurrences: [], facts: [], summary: emptySummary() };
|
|
16
|
+
}
|
|
17
|
+
const lines = sourceLines(input.sourceText);
|
|
18
|
+
const identifiers = declarationIdentifierMap(input, declarations, lines);
|
|
19
|
+
const records = { relations: [], occurrences: [], facts: [], seen: new Set() };
|
|
20
|
+
for (const scan of declarationScanRanges(declarations, lines)) {
|
|
21
|
+
scanDeclarationDependencies(input, documentId, scan, identifiers, lines, records);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
relations: records.relations,
|
|
25
|
+
occurrences: records.occurrences,
|
|
26
|
+
facts: records.facts,
|
|
27
|
+
summary: {
|
|
28
|
+
total: records.relations.length,
|
|
29
|
+
calls: records.relations.filter((relation) => relation.predicate === 'calls').length,
|
|
30
|
+
uses: records.relations.filter((relation) => relation.predicate === 'uses').length
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function declarationIdentifierMap(input, declarations, lines) {
|
|
36
|
+
const identifiers = new Map();
|
|
37
|
+
for (const declaration of declarations ?? []) {
|
|
38
|
+
if (!declaration?.symbolId) continue;
|
|
39
|
+
const target = {
|
|
40
|
+
name: declaration.name,
|
|
41
|
+
symbolId: declaration.symbolId,
|
|
42
|
+
symbolKind: declaration.symbolKind,
|
|
43
|
+
nodeId: declaration.nodeId,
|
|
44
|
+
role: declaration.role
|
|
45
|
+
};
|
|
46
|
+
for (const identifier of identifiersForDeclaration(input, declaration, lines)) {
|
|
47
|
+
addIdentifierTarget(identifiers, identifier, target);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return identifiers;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function identifiersForDeclaration(input, declaration, lines) {
|
|
54
|
+
const identifiers = new Set();
|
|
55
|
+
addIdentifier(identifiers, declaration.name);
|
|
56
|
+
const parts = String(declaration.name ?? '').split('.');
|
|
57
|
+
addIdentifier(identifiers, parts[parts.length - 1]);
|
|
58
|
+
if (declaration.role === 'import') {
|
|
59
|
+
const line = lines[(declaration.span?.startLine ?? 1) - 1]?.line ?? '';
|
|
60
|
+
for (const identifier of importLocalIdentifiers(line)) addIdentifier(identifiers, identifier);
|
|
61
|
+
addIdentifier(identifiers, packageIdentifier(declaration.importPath ?? declaration.name));
|
|
62
|
+
}
|
|
63
|
+
return [...identifiers];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function importLocalIdentifiers(line) {
|
|
67
|
+
const source = String(line ?? '');
|
|
68
|
+
const identifiers = [];
|
|
69
|
+
let match = source.match(/^import\s+([A-Za-z_$][\w$]*)\s+from\b/);
|
|
70
|
+
if (match) identifiers.push(match[1]);
|
|
71
|
+
match = source.match(/^import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\b/);
|
|
72
|
+
if (match) identifiers.push(match[1]);
|
|
73
|
+
match = source.match(/^import\s+[^,{]+,\s*\{([^}]+)\}\s+from\b/);
|
|
74
|
+
if (match) identifiers.push(...namedImportIdentifiers(match[1]));
|
|
75
|
+
match = source.match(/^import\s+\{([^}]+)\}\s+from\b/);
|
|
76
|
+
if (match) identifiers.push(...namedImportIdentifiers(match[1]));
|
|
77
|
+
return identifiers;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function namedImportIdentifiers(raw) {
|
|
81
|
+
return String(raw ?? '')
|
|
82
|
+
.split(',')
|
|
83
|
+
.map((part) => part.trim().split(/\s+as\s+/i).pop()?.trim())
|
|
84
|
+
.filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function packageIdentifier(value) {
|
|
88
|
+
const parts = String(value ?? '').split('/').filter(Boolean);
|
|
89
|
+
const last = parts[parts.length - 1] ?? value;
|
|
90
|
+
return String(last).replace(/[^A-Za-z0-9_$]/g, '');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function addIdentifierTarget(map, identifier, target) {
|
|
94
|
+
if (!isIdentifier(identifier)) return;
|
|
95
|
+
const existing = map.get(identifier) ?? [];
|
|
96
|
+
if (!existing.some((entry) => entry.symbolId === target.symbolId)) existing.push(target);
|
|
97
|
+
map.set(identifier, existing);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function addIdentifier(set, value) {
|
|
101
|
+
if (isIdentifier(value)) set.add(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isIdentifier(value) {
|
|
105
|
+
const text = String(value ?? '');
|
|
106
|
+
return /^[A-Za-z_$][\w$]*$/.test(text) && !ignoredIdentifiers.has(text);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function declarationScanRanges(declarations, lines) {
|
|
110
|
+
return (declarations ?? [])
|
|
111
|
+
.filter((declaration) => declaration?.symbolId && declaration.role !== 'import')
|
|
112
|
+
.map((declaration) => ({
|
|
113
|
+
declaration,
|
|
114
|
+
startLine: declaration.span?.startLine ?? 1,
|
|
115
|
+
endLine: declarationScanEndLine(declaration, lines)
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function declarationScanEndLine(declaration, lines) {
|
|
120
|
+
const startLine = declaration.span?.startLine ?? 1;
|
|
121
|
+
if (!declaration.metadata?.hasBody || declaration.kind === 'ClassDeclaration') return startLine;
|
|
122
|
+
return balancedRegionEndLine(lines, startLine);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function balancedRegionEndLine(lines, startLine) {
|
|
126
|
+
const state = { inBlockComment: false, inTemplateString: false };
|
|
127
|
+
let depth = 0;
|
|
128
|
+
let opened = false;
|
|
129
|
+
for (let index = Math.max(0, startLine - 1); index < lines.length; index += 1) {
|
|
130
|
+
for (const char of maskReferenceLine(lines[index].line, state)) {
|
|
131
|
+
if (char === '{' || char === '[' || char === '(') {
|
|
132
|
+
depth += 1;
|
|
133
|
+
opened = true;
|
|
134
|
+
} else if (char === '}' || char === ']' || char === ')') {
|
|
135
|
+
depth -= 1;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (opened && depth <= 0) return lines[index].number;
|
|
139
|
+
}
|
|
140
|
+
return startLine;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function scanDeclarationDependencies(input, documentId, scan, identifiers, lines, records) {
|
|
144
|
+
const state = { inBlockComment: false, inTemplateString: false };
|
|
145
|
+
for (let lineNumber = scan.startLine; lineNumber <= scan.endLine; lineNumber += 1) {
|
|
146
|
+
const scanLine = maskReferenceLine(lines[lineNumber - 1]?.line ?? '', state);
|
|
147
|
+
for (const match of scanLine.matchAll(/[A-Za-z_$][\w$]*/g)) {
|
|
148
|
+
const name = match[0];
|
|
149
|
+
if (ignoredIdentifiers.has(name) || !identifiers.has(name)) continue;
|
|
150
|
+
const targets = identifiers.get(name).filter((target) => target.symbolId !== scan.declaration.symbolId);
|
|
151
|
+
for (const target of targets) {
|
|
152
|
+
addDependencyRecord(input, documentId, scan.declaration, target, {
|
|
153
|
+
line: scanLine,
|
|
154
|
+
lineNumber,
|
|
155
|
+
startColumn: match.index + 1,
|
|
156
|
+
name
|
|
157
|
+
}, records);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function addDependencyRecord(input, documentId, caller, target, occurrence, records) {
|
|
164
|
+
const predicate = isCallReference(occurrence.line, occurrence.startColumn + occurrence.name.length - 1) ? 'calls' : 'uses';
|
|
165
|
+
const key = `${caller.symbolId}|${predicate}|${target.symbolId}|${occurrence.lineNumber}|${occurrence.startColumn}`;
|
|
166
|
+
if (records.seen.has(key)) return;
|
|
167
|
+
records.seen.add(key);
|
|
168
|
+
const relationId = `rel_${idFragment(caller.symbolId)}_${predicate}_${idFragment(target.symbolId)}_${occurrence.lineNumber}_${occurrence.startColumn}`;
|
|
169
|
+
const occurrenceId = `occ_${idFragment(caller.nodeId)}_${predicate}_${idFragment(target.symbolId)}_${occurrence.lineNumber}_${occurrence.startColumn}`;
|
|
170
|
+
const span = {
|
|
171
|
+
sourceId: input.sourceHash,
|
|
172
|
+
path: input.sourcePath,
|
|
173
|
+
startLine: occurrence.lineNumber,
|
|
174
|
+
endLine: occurrence.lineNumber,
|
|
175
|
+
startColumn: occurrence.startColumn,
|
|
176
|
+
endColumn: occurrence.startColumn + occurrence.name.length
|
|
177
|
+
};
|
|
178
|
+
records.relations.push({
|
|
179
|
+
id: relationId,
|
|
180
|
+
sourceId: caller.symbolId,
|
|
181
|
+
predicate,
|
|
182
|
+
targetId: target.symbolId,
|
|
183
|
+
metadata: {
|
|
184
|
+
scan: 'lightweight-dependency',
|
|
185
|
+
confidence: 'lexical-reference',
|
|
186
|
+
sourceDocumentId: documentId,
|
|
187
|
+
sourceName: caller.name,
|
|
188
|
+
targetName: target.name
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
records.occurrences.push({
|
|
192
|
+
id: occurrenceId,
|
|
193
|
+
documentId,
|
|
194
|
+
symbolId: target.symbolId,
|
|
195
|
+
role: 'reference',
|
|
196
|
+
span,
|
|
197
|
+
nativeAstNodeId: caller.nodeId
|
|
198
|
+
});
|
|
199
|
+
records.facts.push({
|
|
200
|
+
id: `fact_${idFragment(relationId)}_lightweight_dependency`,
|
|
201
|
+
predicate: 'lightweightDependency',
|
|
202
|
+
subjectId: caller.symbolId,
|
|
203
|
+
value: {
|
|
204
|
+
relationId,
|
|
205
|
+
predicate,
|
|
206
|
+
targetId: target.symbolId,
|
|
207
|
+
line: occurrence.lineNumber,
|
|
208
|
+
confidence: 'lexical-reference'
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function isCallReference(line, afterIdentifierIndex) {
|
|
214
|
+
return /^\s*\(/.test(String(line ?? '').slice(afterIdentifierIndex));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function maskReferenceLine(line, state) {
|
|
218
|
+
const text = String(line ?? '');
|
|
219
|
+
let output = '';
|
|
220
|
+
let quote;
|
|
221
|
+
let escaped = false;
|
|
222
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
223
|
+
const char = text[index];
|
|
224
|
+
const next = text[index + 1];
|
|
225
|
+
if (state.inBlockComment) {
|
|
226
|
+
output += ' ';
|
|
227
|
+
if (char === '*' && next === '/') {
|
|
228
|
+
output += ' ';
|
|
229
|
+
index += 1;
|
|
230
|
+
state.inBlockComment = false;
|
|
231
|
+
}
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (state.inTemplateString) {
|
|
235
|
+
output += ' ';
|
|
236
|
+
if (char === '`' && !escaped) state.inTemplateString = false;
|
|
237
|
+
escaped = char === '\\' && !escaped;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (quote) {
|
|
241
|
+
output += ' ';
|
|
242
|
+
if (escaped) escaped = false;
|
|
243
|
+
else if (char === '\\') escaped = true;
|
|
244
|
+
else if (char === quote) quote = undefined;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (char === '/' && next === '/') break;
|
|
248
|
+
if (char === '/' && next === '*') {
|
|
249
|
+
output += ' ';
|
|
250
|
+
index += 1;
|
|
251
|
+
state.inBlockComment = true;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (char === '\'' || char === '"') {
|
|
255
|
+
output += ' ';
|
|
256
|
+
quote = char;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (char === '`') {
|
|
260
|
+
output += ' ';
|
|
261
|
+
state.inTemplateString = true;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
output += char;
|
|
265
|
+
}
|
|
266
|
+
return output;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function emptySummary() {
|
|
270
|
+
return { total: 0, calls: 0, uses: 0 };
|
|
271
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { countBy, uniqueRecordsById, uniqueStrings } from './native-import-utils.js';
|
|
2
|
+
|
|
3
|
+
function summarizeSemanticImportDependencies(imports) {
|
|
4
|
+
return summarizeSemanticImportDependencyRelations((imports ?? [])
|
|
5
|
+
.flatMap((imported) => imported?.semanticIndex?.relations ?? imported?.universalAst?.semanticIndex?.relations ?? []));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function summarizeSemanticImportDependencyRelations(relations) {
|
|
9
|
+
const dependencyRelations = uniqueRecordsById((relations ?? []).filter(isDependencyRelation));
|
|
10
|
+
const predicateKeys = dependencyRelations.map((relation) => semanticDependencyPredicateKey(relation.predicate));
|
|
11
|
+
const byPredicate = countBy(predicateKeys);
|
|
12
|
+
return {
|
|
13
|
+
total: dependencyRelations.length,
|
|
14
|
+
calls: byPredicate.calls ?? 0,
|
|
15
|
+
uses: byPredicate.uses ?? 0,
|
|
16
|
+
references: byPredicate.references ?? 0,
|
|
17
|
+
imports: byPredicate.imports ?? 0,
|
|
18
|
+
extends: byPredicate.extends ?? 0,
|
|
19
|
+
implements: byPredicate.implements ?? 0,
|
|
20
|
+
includes: byPredicate.includes ?? 0,
|
|
21
|
+
requires: byPredicate.requires ?? 0,
|
|
22
|
+
byPredicate,
|
|
23
|
+
predicates: uniqueStrings(predicateKeys),
|
|
24
|
+
ids: dependencyRelations.map((relation) => relation.id).filter(Boolean),
|
|
25
|
+
sourceSymbolIds: uniqueStrings(dependencyRelations.map((relation) => relation.sourceId).filter(Boolean)),
|
|
26
|
+
targetSymbolIds: uniqueStrings(dependencyRelations.map((relation) => relation.targetId).filter(Boolean))
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isDependencyRelation(relation) {
|
|
31
|
+
const predicate = String(relation?.predicate ?? '').toLowerCase();
|
|
32
|
+
if (!predicate || predicate === 'defines' || predicate === 'definitionof') return false;
|
|
33
|
+
return predicate === 'imports'
|
|
34
|
+
|| predicate === 'calls'
|
|
35
|
+
|| predicate === 'uses'
|
|
36
|
+
|| predicate.includes('reference')
|
|
37
|
+
|| predicate.includes('depend')
|
|
38
|
+
|| predicate.includes('require')
|
|
39
|
+
|| predicate.includes('include')
|
|
40
|
+
|| predicate.includes('extend')
|
|
41
|
+
|| predicate.includes('implement');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function semanticDependencyPredicateKey(predicate) {
|
|
45
|
+
const value = String(predicate ?? 'unknown').toLowerCase();
|
|
46
|
+
if (value.includes('call')) return 'calls';
|
|
47
|
+
if (value.includes('reference')) return 'references';
|
|
48
|
+
if (value.includes('import')) return 'imports';
|
|
49
|
+
if (value.includes('depend')) return 'depends';
|
|
50
|
+
if (value.includes('require')) return 'requires';
|
|
51
|
+
if (value.includes('include')) return 'includes';
|
|
52
|
+
if (value.includes('extend')) return 'extends';
|
|
53
|
+
if (value.includes('implement')) return 'implements';
|
|
54
|
+
if (value === 'uses') return 'uses';
|
|
55
|
+
return value || 'unknown';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
semanticDependencyPredicateKey,
|
|
60
|
+
summarizeSemanticImportDependencies,
|
|
61
|
+
summarizeSemanticImportDependencyRelations
|
|
62
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { uniqueRecordsById, uniqueStrings } from './native-import-utils.js';
|
|
2
|
+
import { summarizeSemanticImportDependencyRelations } from './semantic-import-dependencies.js';
|
|
2
3
|
import { semanticOwnershipRegionForSymbol, summarizeSemanticImportRegionTaxonomy } from './semantic-import-regions.js';
|
|
3
4
|
import { collectKernelSourcePreservationFromImport } from './semantic-import-source-preservation.js';
|
|
4
5
|
import {
|
|
@@ -16,6 +17,8 @@ function semanticImportSidecarEntry(imported, index, options) {
|
|
|
16
17
|
const universalAstLayers = summarizeUniversalAstLayers(imported?.universalAst);
|
|
17
18
|
const proofSpec = summarizeProofSpecLayer(imported?.universalAst?.proof ?? imported?.proof);
|
|
18
19
|
const paradigmSemantics = summarizeParadigmSemanticsLayer(imported?.universalAst?.paradigmSemantics ?? imported?.paradigmSemantics);
|
|
20
|
+
const dependencies = summarizeSemanticImportDependencyRelations(semanticIndex?.relations ?? []);
|
|
21
|
+
const readiness = semanticImportEntryReadiness(imported);
|
|
19
22
|
const mappingsBySymbolId = new Map();
|
|
20
23
|
for (const mapping of sourceMapMappings) {
|
|
21
24
|
if (mapping.semanticSymbolId && !mappingsBySymbolId.has(mapping.semanticSymbolId)) {
|
|
@@ -42,7 +45,7 @@ function semanticImportSidecarEntry(imported, index, options) {
|
|
|
42
45
|
ownershipRegionId: region.id,
|
|
43
46
|
ownershipKey: region.key,
|
|
44
47
|
ownershipRegionKind: region.regionKind,
|
|
45
|
-
readiness
|
|
48
|
+
readiness
|
|
46
49
|
});
|
|
47
50
|
}
|
|
48
51
|
const ownershipRegions = uniqueRecordsById(regions);
|
|
@@ -67,7 +70,9 @@ function semanticImportSidecarEntry(imported, index, options) {
|
|
|
67
70
|
universalAstLayerIds: universalAstLayers.ids,
|
|
68
71
|
proofSpec,
|
|
69
72
|
paradigmSemantics,
|
|
70
|
-
|
|
73
|
+
dependencyRelationCount: dependencies.total,
|
|
74
|
+
dependencyPredicates: dependencies.predicates,
|
|
75
|
+
readiness,
|
|
71
76
|
emptySemanticIndex: symbols.length === 0,
|
|
72
77
|
regionTaxonomy,
|
|
73
78
|
symbols,
|
|
@@ -75,4 +80,14 @@ function semanticImportSidecarEntry(imported, index, options) {
|
|
|
75
80
|
};
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
function semanticImportEntryReadiness(imported) {
|
|
84
|
+
const readiness = imported?.metadata?.semanticMergeReadiness
|
|
85
|
+
?? imported?.metadata?.nativeImportLossSummary?.semanticMergeReadiness
|
|
86
|
+
?? imported?.readiness?.semanticMergeReadiness
|
|
87
|
+
?? imported?.readiness?.readiness
|
|
88
|
+
?? (typeof imported?.readiness === 'string' ? imported.readiness : undefined)
|
|
89
|
+
?? imported?.mergeCandidates?.[0]?.readiness;
|
|
90
|
+
return readiness ?? 'needs-review';
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
export { semanticImportSidecarEntry };
|
|
@@ -90,6 +90,8 @@ export interface SemanticImportSidecarImportEntry {
|
|
|
90
90
|
readonly universalAstLayerIds: readonly string[];
|
|
91
91
|
readonly proofSpec: SemanticImportSidecarProofSpecSummary;
|
|
92
92
|
readonly paradigmSemantics: SemanticImportSidecarParadigmSemanticsSummary;
|
|
93
|
+
readonly dependencyRelationCount: number;
|
|
94
|
+
readonly dependencyPredicates: readonly string[];
|
|
93
95
|
readonly readiness: SemanticMergeReadiness;
|
|
94
96
|
readonly emptySemanticIndex: boolean;
|
|
95
97
|
readonly regionTaxonomy?: SemanticImportRegionTaxonomySummary;
|
|
@@ -186,6 +188,23 @@ export interface SemanticImportSidecarParadigmSemanticsSummary {
|
|
|
186
188
|
readonly empty: boolean;
|
|
187
189
|
}
|
|
188
190
|
|
|
191
|
+
export interface SemanticImportDependencySummary {
|
|
192
|
+
readonly total: number;
|
|
193
|
+
readonly calls: number;
|
|
194
|
+
readonly uses: number;
|
|
195
|
+
readonly references: number;
|
|
196
|
+
readonly imports: number;
|
|
197
|
+
readonly extends: number;
|
|
198
|
+
readonly implements: number;
|
|
199
|
+
readonly includes: number;
|
|
200
|
+
readonly requires: number;
|
|
201
|
+
readonly byPredicate: Readonly<Record<string, number>>;
|
|
202
|
+
readonly predicates: readonly string[];
|
|
203
|
+
readonly ids: readonly string[];
|
|
204
|
+
readonly sourceSymbolIds: readonly string[];
|
|
205
|
+
readonly targetSymbolIds: readonly string[];
|
|
206
|
+
}
|
|
207
|
+
|
|
189
208
|
export interface SemanticImportSidecar {
|
|
190
209
|
readonly kind: 'frontier.lang.semanticImportSidecar';
|
|
191
210
|
readonly version: 1;
|
|
@@ -217,6 +236,7 @@ export interface SemanticImportSidecar {
|
|
|
217
236
|
readonly universalAstLayers: SemanticImportSidecarUniversalAstLayerSummary;
|
|
218
237
|
readonly proofSpec: SemanticImportSidecarProofSpecSummary;
|
|
219
238
|
readonly paradigmSemantics: SemanticImportSidecarParadigmSemanticsSummary;
|
|
239
|
+
readonly dependencies: SemanticImportDependencySummary;
|
|
220
240
|
readonly patchHints: readonly SemanticImportPatchHint[];
|
|
221
241
|
readonly quality: SemanticImportSidecarQuality;
|
|
222
242
|
readonly admission: SemanticImportSidecarAdmission;
|
|
@@ -258,6 +278,8 @@ export interface SemanticImportSidecar {
|
|
|
258
278
|
readonly paradigmSemanticsRecords: number;
|
|
259
279
|
readonly paradigmSemanticsGroups: number;
|
|
260
280
|
readonly paradigmSemanticsLoweringRecords: number;
|
|
281
|
+
readonly dependencyRelations: number;
|
|
282
|
+
readonly dependencyPredicates: readonly string[];
|
|
261
283
|
readonly patchHints: number;
|
|
262
284
|
readonly evidenceWarnings: number;
|
|
263
285
|
readonly readiness: SemanticMergeReadiness;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compileFrontierSource,
|
|
3
|
+
compileNativeSource,
|
|
4
|
+
importNativeSource,
|
|
5
|
+
writeUniversalAstJson
|
|
6
|
+
} from '../dist/index.js';
|
|
7
|
+
|
|
8
|
+
const javascriptSource = `import { nanoid } from "nanoid";
|
|
9
|
+
|
|
10
|
+
export function addTodo(title) {
|
|
11
|
+
return { id: nanoid(), title };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class TodoStore {
|
|
15
|
+
save(title) {
|
|
16
|
+
return addTodo(title);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const imported = importNativeSource({
|
|
22
|
+
language: 'javascript',
|
|
23
|
+
sourcePath: 'src/todo.js',
|
|
24
|
+
sourceText: javascriptSource
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const graphSummary = {
|
|
28
|
+
universalAst: imported.universalAst.kind,
|
|
29
|
+
semanticIndexId: imported.semanticIndex.id,
|
|
30
|
+
symbols: imported.semanticIndex.symbols.map((symbol) => ({
|
|
31
|
+
name: symbol.name,
|
|
32
|
+
kind: symbol.kind,
|
|
33
|
+
region: symbol.metadata?.ownershipRegionKind
|
|
34
|
+
})),
|
|
35
|
+
sourceMapMappings: imported.sourceMaps[0]?.mappings.length ?? 0,
|
|
36
|
+
mergeReadiness: imported.metadata.nativeImportLossSummary.semanticMergeReadiness,
|
|
37
|
+
losses: imported.losses.map((loss) => loss.kind)
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
console.log('--- JavaScript source ---');
|
|
41
|
+
console.log(javascriptSource.trim());
|
|
42
|
+
console.log('\n--- Frontier graph summary ---');
|
|
43
|
+
console.log(JSON.stringify(graphSummary, null, 2));
|
|
44
|
+
console.log('\n--- Universal AST envelope excerpt ---');
|
|
45
|
+
console.log(JSON.stringify(universalAstExcerpt(imported), null, 2));
|
|
46
|
+
|
|
47
|
+
const stubProjection = compileNativeSource(imported, {
|
|
48
|
+
target: 'rust',
|
|
49
|
+
targetPath: 'src/todo.rs'
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
console.log('\n--- Rust declaration stubs without a target adapter ---');
|
|
53
|
+
console.log(`ok=${stubProjection.ok} readiness=${stubProjection.readiness.readiness} mode=${stubProjection.outputMode}`);
|
|
54
|
+
console.log(stubProjection.output.trim());
|
|
55
|
+
|
|
56
|
+
const adapterProjection = compileNativeSource(imported, {
|
|
57
|
+
target: 'rust',
|
|
58
|
+
targetPath: 'src/todo.rs',
|
|
59
|
+
targetAdapters: [createDemoJsToRustAdapter()]
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
console.log('\n--- Rust output with a host-owned target adapter ---');
|
|
63
|
+
console.log(`ok=${adapterProjection.ok} readiness=${adapterProjection.readiness.readiness} mode=${adapterProjection.outputMode}`);
|
|
64
|
+
console.log(adapterProjection.output.trim());
|
|
65
|
+
|
|
66
|
+
const frontierSource = `module TodoApp @id("mod_todo")
|
|
67
|
+
|
|
68
|
+
type TodoInput @id("type_todo_input") {
|
|
69
|
+
title: Text
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
entity Todo @id("ent_todo") {
|
|
73
|
+
title @id("field_title"): Text
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
action addTodo @id("action_add") {
|
|
77
|
+
input TodoInput
|
|
78
|
+
writes field_title
|
|
79
|
+
returns Patch
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
const canonicalRust = compileFrontierSource(frontierSource, { target: 'rust' });
|
|
84
|
+
|
|
85
|
+
console.log('\n--- Frontier source projected directly to Rust ---');
|
|
86
|
+
console.log(`ok=${canonicalRust.ok} target=${canonicalRust.target}`);
|
|
87
|
+
console.log(canonicalRust.output.trim());
|
|
88
|
+
|
|
89
|
+
function universalAstExcerpt(importResult) {
|
|
90
|
+
const parsed = JSON.parse(writeUniversalAstJson(importResult.universalAst));
|
|
91
|
+
return {
|
|
92
|
+
kind: parsed.kind,
|
|
93
|
+
id: parsed.id,
|
|
94
|
+
documentCount: parsed.documents?.length ?? 0,
|
|
95
|
+
symbolCount: parsed.semanticIndex?.symbols?.length ?? 0,
|
|
96
|
+
sourceMapCount: parsed.sourceMaps?.length ?? 0
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function createDemoJsToRustAdapter() {
|
|
101
|
+
return {
|
|
102
|
+
id: 'demo-js-to-rust-target-adapter',
|
|
103
|
+
sourceLanguage: 'javascript',
|
|
104
|
+
target: 'rust',
|
|
105
|
+
version: '0.0.0-demo',
|
|
106
|
+
capabilities: ['declaration-stubs'],
|
|
107
|
+
coverage: {
|
|
108
|
+
readiness: 'ready',
|
|
109
|
+
handledLossKinds: ['opaqueNative', 'declarationOnlyCoverage', 'partialSemanticIndex', 'sourceMapApproximation', 'sourcePreservation'],
|
|
110
|
+
notes: ['Demo adapter turns Frontier semantic symbols into deterministic Rust scaffolding.']
|
|
111
|
+
},
|
|
112
|
+
project(input) {
|
|
113
|
+
const symbols = input.importResult.semanticIndex?.symbols ?? [];
|
|
114
|
+
return {
|
|
115
|
+
output: renderRustScaffold(symbols),
|
|
116
|
+
readiness: 'ready',
|
|
117
|
+
evidence: [{
|
|
118
|
+
id: 'evidence_demo_js_to_rust_adapter',
|
|
119
|
+
kind: 'projection',
|
|
120
|
+
status: 'passed',
|
|
121
|
+
summary: 'Demo adapter projected JavaScript semantic symbols to Rust scaffolding.'
|
|
122
|
+
}]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function renderRustScaffold(symbols) {
|
|
129
|
+
const lines = ['// Generated from Frontier semantic graph evidence.'];
|
|
130
|
+
for (const symbol of symbols) {
|
|
131
|
+
if (symbol.kind === 'class') {
|
|
132
|
+
lines.push(`pub struct ${symbol.name};`, '');
|
|
133
|
+
} else if (symbol.kind === 'function' || symbol.kind === 'method') {
|
|
134
|
+
lines.push(`pub fn ${rustName(symbol.name)}() {`);
|
|
135
|
+
lines.push(' todo!("port body from native source evidence");');
|
|
136
|
+
lines.push('}', '');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return `${lines.join('\n').trim()}\n`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function rustName(name) {
|
|
143
|
+
return String(name)
|
|
144
|
+
.replace(/\./g, '_')
|
|
145
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
146
|
+
.replace(/[^A-Za-z0-9_]/g, '_')
|
|
147
|
+
.toLowerCase();
|
|
148
|
+
}
|
package/package.json
CHANGED