@optave/codegraph 3.12.0 → 3.13.0
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 +71 -35
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +2 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/batch.d.ts.map +1 -1
- package/dist/cli/commands/batch.js +1 -0
- package/dist/cli/commands/batch.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +6 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +272 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/triage.js +1 -1
- package/dist/cli/commands/triage.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +10 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/shared/options.d.ts +2 -1
- package/dist/cli/shared/options.d.ts.map +1 -1
- package/dist/cli/shared/options.js +11 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/cli/types.d.ts +2 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/db/migrations.js +1 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/domain/graph/builder/call-resolver.d.ts +12 -8
- package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -1
- package/dist/domain/graph/builder/call-resolver.js +93 -38
- package/dist/domain/graph/builder/call-resolver.js.map +1 -1
- package/dist/domain/graph/builder/cha.d.ts +9 -1
- package/dist/domain/graph/builder/cha.d.ts.map +1 -1
- package/dist/domain/graph/builder/cha.js +17 -2
- package/dist/domain/graph/builder/cha.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +8 -0
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +22 -3
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +1 -1
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +37 -2
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts +0 -2
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +88 -318
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +4 -0
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.js +341 -82
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/parser.d.ts +4 -5
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +46 -15
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +10 -2
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/domain/wasm-worker-pool.d.ts.map +1 -1
- package/dist/domain/wasm-worker-pool.js +2 -0
- package/dist/domain/wasm-worker-pool.js.map +1 -1
- package/dist/domain/wasm-worker-protocol.d.ts +1 -0
- package/dist/domain/wasm-worker-protocol.d.ts.map +1 -1
- package/dist/extractors/cpp.d.ts.map +1 -1
- package/dist/extractors/cpp.js +42 -1
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/cuda.d.ts.map +1 -1
- package/dist/extractors/cuda.js +42 -1
- package/dist/extractors/cuda.js.map +1 -1
- package/dist/extractors/helpers.d.ts +11 -0
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +40 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +8 -7
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.js +137 -6
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/features/structure-query.d.ts +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +6 -6
- package/dist/features/structure-query.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/config.d.ts +77 -4
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +395 -21
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/registry.d.ts +27 -0
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +59 -1
- package/dist/infrastructure/registry.js.map +1 -1
- package/dist/presentation/structure.d.ts +1 -1
- package/dist/presentation/structure.d.ts.map +1 -1
- package/dist/presentation/structure.js +2 -2
- package/dist/presentation/structure.js.map +1 -1
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +7 -8
- package/src/cli/commands/audit.ts +2 -1
- package/src/cli/commands/batch.ts +1 -0
- package/src/cli/commands/build.ts +6 -1
- package/src/cli/commands/config.ts +353 -0
- package/src/cli/commands/triage.ts +1 -1
- package/src/cli/index.ts +10 -0
- package/src/cli/shared/options.ts +11 -1
- package/src/cli/types.ts +2 -0
- package/src/db/migrations.ts +1 -1
- package/src/domain/graph/builder/call-resolver.ts +99 -41
- package/src/domain/graph/builder/cha.ts +18 -1
- package/src/domain/graph/builder/helpers.ts +24 -4
- package/src/domain/graph/builder/incremental.ts +1 -0
- package/src/domain/graph/builder/pipeline.ts +49 -2
- package/src/domain/graph/builder/stages/build-edges.ts +130 -399
- package/src/domain/graph/builder/stages/detect-changes.ts +1 -1
- package/src/domain/graph/builder/stages/finalize.ts +4 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +396 -92
- package/src/domain/graph/builder/stages/resolve-imports.ts +1 -1
- package/src/domain/parser.ts +45 -14
- package/src/domain/wasm-worker-entry.ts +10 -2
- package/src/domain/wasm-worker-pool.ts +1 -0
- package/src/domain/wasm-worker-protocol.ts +1 -0
- package/src/extractors/cpp.ts +44 -1
- package/src/extractors/cuda.ts +44 -1
- package/src/extractors/helpers.ts +43 -0
- package/src/extractors/java.ts +8 -7
- package/src/extractors/javascript.ts +127 -6
- package/src/features/structure-query.ts +7 -7
- package/src/index.ts +5 -1
- package/src/infrastructure/config.ts +481 -22
- package/src/infrastructure/registry.ts +82 -1
- package/src/presentation/structure.ts +3 -3
- package/src/types.ts +41 -0
- package/grammars/tree-sitter-erlang.wasm +0 -0
|
@@ -12,15 +12,23 @@ import { PROPAGATION_HOP_PENALTY } from '../../../../extractors/javascript.js';
|
|
|
12
12
|
import { debug } from '../../../../infrastructure/logger.js';
|
|
13
13
|
import { loadNative } from '../../../../infrastructure/native.js';
|
|
14
14
|
import type {
|
|
15
|
+
ArrayCallbackBinding,
|
|
16
|
+
ArrayElemBinding,
|
|
15
17
|
BetterSqlite3Database,
|
|
16
18
|
Call,
|
|
17
19
|
ClassRelation,
|
|
18
20
|
Definition,
|
|
19
21
|
ExtractorOutput,
|
|
20
22
|
FnRefBinding,
|
|
23
|
+
ForOfBinding,
|
|
21
24
|
Import,
|
|
22
25
|
NativeAddon,
|
|
23
26
|
NodeRow,
|
|
27
|
+
ObjectPropBinding,
|
|
28
|
+
ObjectRestParamBinding,
|
|
29
|
+
ParamBinding,
|
|
30
|
+
SpreadArgBinding,
|
|
31
|
+
ThisCallBinding,
|
|
24
32
|
TypeMapEntry,
|
|
25
33
|
} from '../../../../types.js';
|
|
26
34
|
import { computeConfidence } from '../../resolve.js';
|
|
@@ -37,7 +45,13 @@ import {
|
|
|
37
45
|
import type { ChaContext } from '../cha.js';
|
|
38
46
|
import { buildChaContext, resolveChaTargets, resolveThisDispatch } from '../cha.js';
|
|
39
47
|
import type { PipelineContext } from '../context.js';
|
|
40
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
BUILTIN_RECEIVERS,
|
|
50
|
+
batchInsertEdges,
|
|
51
|
+
CHA_DISPATCH_PENALTY,
|
|
52
|
+
CHA_TYPED_DISPATCH_CONFIDENCE,
|
|
53
|
+
runChaPostPass,
|
|
54
|
+
} from '../helpers.js';
|
|
41
55
|
import { getResolved, isBarrelFile, resolveBarrelExportCached } from './resolve-imports.js';
|
|
42
56
|
|
|
43
57
|
// ── Local types ──────────────────────────────────────────────────────────
|
|
@@ -61,13 +75,27 @@ interface QueryNodeRow {
|
|
|
61
75
|
interface NativeFileEntry {
|
|
62
76
|
file: string;
|
|
63
77
|
fileNodeId: number;
|
|
64
|
-
definitions: Array<{
|
|
78
|
+
definitions: Array<{
|
|
79
|
+
name: string;
|
|
80
|
+
kind: string;
|
|
81
|
+
line: number;
|
|
82
|
+
endLine: number | null;
|
|
83
|
+
params?: string[];
|
|
84
|
+
}>;
|
|
65
85
|
calls: Call[];
|
|
66
86
|
importedNames: Array<{ name: string; file: string }>;
|
|
67
87
|
classes: ClassRelation[];
|
|
68
88
|
typeMap: Array<{ name: string; typeName: string; confidence: number }>;
|
|
69
89
|
/** Phase 8.3: function-reference bindings for pts analysis. */
|
|
70
90
|
fnRefBindings?: Array<{ lhs: string; rhs: string; rhsReceiver?: string }>;
|
|
91
|
+
paramBindings?: ParamBinding[];
|
|
92
|
+
thisCallBindings?: ThisCallBinding[];
|
|
93
|
+
arrayElemBindings?: ArrayElemBinding[];
|
|
94
|
+
spreadArgBindings?: SpreadArgBinding[];
|
|
95
|
+
forOfBindings?: ForOfBinding[];
|
|
96
|
+
arrayCallbackBindings?: ArrayCallbackBinding[];
|
|
97
|
+
objectRestParamBindings?: ObjectRestParamBinding[];
|
|
98
|
+
objectPropBindings?: ObjectPropBinding[];
|
|
71
99
|
}
|
|
72
100
|
|
|
73
101
|
/** Shape returned by native buildCallEdges. */
|
|
@@ -79,9 +107,6 @@ interface NativeEdge {
|
|
|
79
107
|
dynamic: number;
|
|
80
108
|
}
|
|
81
109
|
|
|
82
|
-
/** Phase 8.5: confidence penalty applied to CHA-dispatch edges. */
|
|
83
|
-
export const CHA_DISPATCH_PENALTY = 0.1;
|
|
84
|
-
|
|
85
110
|
// ── Node lookup setup ───────────────────────────────────────────────────
|
|
86
111
|
|
|
87
112
|
function makeGetNodeIdStmt(db: BetterSqlite3Database): NodeIdStmt {
|
|
@@ -509,17 +534,35 @@ function buildCallEdgesNative(
|
|
|
509
534
|
nativeFiles.push({
|
|
510
535
|
file: relPath,
|
|
511
536
|
fileNodeId: fileNodeRow.id,
|
|
512
|
-
definitions: symbols.definitions.map((d) =>
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
537
|
+
definitions: symbols.definitions.map((d) => {
|
|
538
|
+
const params = d.children?.filter((c) => c.kind === 'parameter').map((c) => c.name);
|
|
539
|
+
return {
|
|
540
|
+
name: d.name,
|
|
541
|
+
kind: d.kind,
|
|
542
|
+
line: d.line,
|
|
543
|
+
endLine: d.endLine ?? null,
|
|
544
|
+
params: params?.length ? params : undefined,
|
|
545
|
+
};
|
|
546
|
+
}),
|
|
518
547
|
calls: symbols.calls,
|
|
519
548
|
importedNames,
|
|
520
549
|
classes: symbols.classes,
|
|
521
550
|
typeMap,
|
|
522
551
|
fnRefBindings: symbols.fnRefBindings?.length ? symbols.fnRefBindings : undefined,
|
|
552
|
+
paramBindings: symbols.paramBindings?.length ? symbols.paramBindings : undefined,
|
|
553
|
+
thisCallBindings: symbols.thisCallBindings?.length ? symbols.thisCallBindings : undefined,
|
|
554
|
+
arrayElemBindings: symbols.arrayElemBindings?.length ? symbols.arrayElemBindings : undefined,
|
|
555
|
+
spreadArgBindings: symbols.spreadArgBindings?.length ? symbols.spreadArgBindings : undefined,
|
|
556
|
+
forOfBindings: symbols.forOfBindings?.length ? symbols.forOfBindings : undefined,
|
|
557
|
+
arrayCallbackBindings: symbols.arrayCallbackBindings?.length
|
|
558
|
+
? symbols.arrayCallbackBindings
|
|
559
|
+
: undefined,
|
|
560
|
+
objectRestParamBindings: symbols.objectRestParamBindings?.length
|
|
561
|
+
? symbols.objectRestParamBindings
|
|
562
|
+
: undefined,
|
|
563
|
+
objectPropBindings: symbols.objectPropBindings?.length
|
|
564
|
+
? symbols.objectPropBindings
|
|
565
|
+
: undefined,
|
|
523
566
|
});
|
|
524
567
|
}
|
|
525
568
|
|
|
@@ -538,363 +581,6 @@ function buildCallEdgesNative(
|
|
|
538
581
|
}
|
|
539
582
|
}
|
|
540
583
|
|
|
541
|
-
/**
|
|
542
|
-
* Phase 8.3c pts post-pass for the native call-edge path.
|
|
543
|
-
*
|
|
544
|
-
* The native Rust engine builds call edges without knowledge of paramBindings,
|
|
545
|
-
* so `fn()` calls inside higher-order functions are not resolved to their
|
|
546
|
-
* concrete targets. This JS post-pass runs after the native edge pass and adds
|
|
547
|
-
* only the parameter-flow pts edges that the native engine missed.
|
|
548
|
-
*
|
|
549
|
-
* To avoid duplicating edges already emitted by the native engine, the current
|
|
550
|
-
* allEdgeRows snapshot is used to seed a seenByPair set before processing each
|
|
551
|
-
* file.
|
|
552
|
-
*/
|
|
553
|
-
function buildParamFlowPtsPostPass(
|
|
554
|
-
ctx: PipelineContext,
|
|
555
|
-
getNodeIdStmt: NodeIdStmt,
|
|
556
|
-
allEdgeRows: EdgeRowTuple[],
|
|
557
|
-
sharedLookup?: CallNodeLookup,
|
|
558
|
-
): void {
|
|
559
|
-
// Only process files that actually have paramBindings (avoid useless work).
|
|
560
|
-
const filesWithParams = [...ctx.fileSymbols].filter(
|
|
561
|
-
([, symbols]) => symbols.paramBindings && symbols.paramBindings.length > 0,
|
|
562
|
-
);
|
|
563
|
-
if (filesWithParams.length === 0) return;
|
|
564
|
-
|
|
565
|
-
// Seed seenByPair from the existing rows so we don't duplicate native edges.
|
|
566
|
-
// This is O(|allEdgeRows|) once per post-pass, which is acceptable.
|
|
567
|
-
const seenByPair = new Set<string>();
|
|
568
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
569
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
573
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
574
|
-
|
|
575
|
-
for (const [relPath, symbols] of filesWithParams) {
|
|
576
|
-
if (barrelOnlyFiles.has(relPath)) continue;
|
|
577
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
578
|
-
if (!fileNodeRow) continue;
|
|
579
|
-
|
|
580
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
581
|
-
const typeMap: Map<string, TypeMapEntry | string> = symbols.typeMap || new Map();
|
|
582
|
-
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
583
|
-
if (!ptsMap) continue;
|
|
584
|
-
|
|
585
|
-
for (const call of symbols.calls) {
|
|
586
|
-
if (call.receiver || call.dynamic) continue; // pts post-pass handles only param-flow (non-dynamic)
|
|
587
|
-
|
|
588
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
589
|
-
const scopedKey = caller.callerName != null ? `${caller.callerName}::${call.name}` : null;
|
|
590
|
-
if (!scopedKey || !ptsMap.has(scopedKey)) continue;
|
|
591
|
-
|
|
592
|
-
// Only resolve calls that had no direct targets (same guard as buildFileCallEdges).
|
|
593
|
-
const { targets } = resolveCallTargets(
|
|
594
|
-
lookup,
|
|
595
|
-
call,
|
|
596
|
-
relPath,
|
|
597
|
-
importedNames,
|
|
598
|
-
typeMap as Map<string, unknown>,
|
|
599
|
-
);
|
|
600
|
-
if (targets.length > 0) continue;
|
|
601
|
-
|
|
602
|
-
for (const alias of resolveViaPointsTo(scopedKey, ptsMap)) {
|
|
603
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
|
|
604
|
-
lookup,
|
|
605
|
-
{ name: alias },
|
|
606
|
-
relPath,
|
|
607
|
-
importedNames,
|
|
608
|
-
typeMap as Map<string, unknown>,
|
|
609
|
-
);
|
|
610
|
-
for (const t of aliasTargets) {
|
|
611
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
612
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
613
|
-
const conf =
|
|
614
|
-
computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
615
|
-
if (conf > 0) {
|
|
616
|
-
seenByPair.add(edgeKey);
|
|
617
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* bind/alias pts post-pass for the native call-edge path.
|
|
628
|
-
*
|
|
629
|
-
* The native Rust engine has no knowledge of JS-layer fnRefBindings (e.g.
|
|
630
|
-
* `const f = fn.bind(ctx)`), so calls to bind-created aliases are not resolved
|
|
631
|
-
* to their original function on the native path. This JS post-pass runs after
|
|
632
|
-
* the native edge pass and adds only the fnRefBindings-seeded pts edges that the
|
|
633
|
-
* native engine missed.
|
|
634
|
-
*
|
|
635
|
-
* Uses the same seenByPair dedup guard as buildParamFlowPtsPostPass to avoid
|
|
636
|
-
* duplicating edges already emitted by the native engine.
|
|
637
|
-
*/
|
|
638
|
-
function buildFnRefBindingsPtsPostPass(
|
|
639
|
-
ctx: PipelineContext,
|
|
640
|
-
getNodeIdStmt: NodeIdStmt,
|
|
641
|
-
allEdgeRows: EdgeRowTuple[],
|
|
642
|
-
sharedLookup?: CallNodeLookup,
|
|
643
|
-
): void {
|
|
644
|
-
// Only process files that actually have fnRefBindings.
|
|
645
|
-
const filesWithBindings = [...ctx.fileSymbols].filter(
|
|
646
|
-
([, symbols]) => symbols.fnRefBindings && symbols.fnRefBindings.length > 0,
|
|
647
|
-
);
|
|
648
|
-
if (filesWithBindings.length === 0) return;
|
|
649
|
-
|
|
650
|
-
// Seed seenByPair from the existing rows so we don't duplicate native edges.
|
|
651
|
-
const seenByPair = new Set<string>();
|
|
652
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
653
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
657
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
658
|
-
|
|
659
|
-
for (const [relPath, symbols] of filesWithBindings) {
|
|
660
|
-
if (barrelOnlyFiles.has(relPath)) continue;
|
|
661
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
662
|
-
if (!fileNodeRow) continue;
|
|
663
|
-
|
|
664
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
665
|
-
const typeMap: Map<string, TypeMapEntry | string> = symbols.typeMap || new Map();
|
|
666
|
-
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
667
|
-
if (!ptsMap) continue;
|
|
668
|
-
|
|
669
|
-
// Only resolve calls whose name is an lhs in fnRefBindings — the same
|
|
670
|
-
// narrowed guard used in buildFileCallEdges case (c).
|
|
671
|
-
const fnRefBindingLhs = new Set(symbols.fnRefBindings!.map((b) => b.lhs));
|
|
672
|
-
|
|
673
|
-
for (const call of symbols.calls) {
|
|
674
|
-
if (call.receiver || call.dynamic) continue; // bind aliases are flat-keyed, never dynamic
|
|
675
|
-
if (!fnRefBindingLhs.has(call.name)) continue;
|
|
676
|
-
if (!ptsMap.has(call.name)) continue;
|
|
677
|
-
|
|
678
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
679
|
-
|
|
680
|
-
// Only resolve calls that had no direct targets (same guard as buildFileCallEdges).
|
|
681
|
-
const { targets } = resolveCallTargets(
|
|
682
|
-
lookup,
|
|
683
|
-
call,
|
|
684
|
-
relPath,
|
|
685
|
-
importedNames,
|
|
686
|
-
typeMap as Map<string, unknown>,
|
|
687
|
-
);
|
|
688
|
-
if (targets.length > 0) continue;
|
|
689
|
-
|
|
690
|
-
for (const alias of resolveViaPointsTo(call.name, ptsMap)) {
|
|
691
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
|
|
692
|
-
lookup,
|
|
693
|
-
{ name: alias },
|
|
694
|
-
relPath,
|
|
695
|
-
importedNames,
|
|
696
|
-
typeMap as Map<string, unknown>,
|
|
697
|
-
);
|
|
698
|
-
for (const t of aliasTargets) {
|
|
699
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
700
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
701
|
-
const conf =
|
|
702
|
-
computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
703
|
-
if (conf > 0) {
|
|
704
|
-
seenByPair.add(edgeKey);
|
|
705
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
* this-rebinding post-pass for the native call-edge path.
|
|
716
|
-
*
|
|
717
|
-
* When `fn.call(namedCtx, ...)` or `fn.apply(namedCtx, ...)` is extracted by the
|
|
718
|
-
* WASM layer, `thisCallBindings` records `{ callee: 'fn', thisArg: 'namedCtx' }`.
|
|
719
|
-
* The native Rust engine has no knowledge of these bindings, so `this()` calls
|
|
720
|
-
* inside `fn` remain unresolved. This JS post-pass adds the missing edges by
|
|
721
|
-
* resolving `this()` calls inside each `fn` that has a thisCallBinding.
|
|
722
|
-
*/
|
|
723
|
-
function buildThisCallBindingsPtsPostPass(
|
|
724
|
-
ctx: PipelineContext,
|
|
725
|
-
getNodeIdStmt: NodeIdStmt,
|
|
726
|
-
allEdgeRows: EdgeRowTuple[],
|
|
727
|
-
sharedLookup?: CallNodeLookup,
|
|
728
|
-
): void {
|
|
729
|
-
const filesWithBindings = [...ctx.fileSymbols].filter(
|
|
730
|
-
([, symbols]) => symbols.thisCallBindings && symbols.thisCallBindings.length > 0,
|
|
731
|
-
);
|
|
732
|
-
if (filesWithBindings.length === 0) return;
|
|
733
|
-
|
|
734
|
-
const seenByPair = new Set<string>();
|
|
735
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
736
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
740
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
741
|
-
|
|
742
|
-
for (const [relPath, symbols] of filesWithBindings) {
|
|
743
|
-
if (barrelOnlyFiles.has(relPath)) continue;
|
|
744
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
745
|
-
if (!fileNodeRow) continue;
|
|
746
|
-
|
|
747
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
748
|
-
const typeMap: Map<string, TypeMapEntry | string> = symbols.typeMap || new Map();
|
|
749
|
-
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
|
|
750
|
-
if (!ptsMap) continue;
|
|
751
|
-
|
|
752
|
-
// Only process calls named 'this' (callee-not-receiver usage)
|
|
753
|
-
for (const call of symbols.calls) {
|
|
754
|
-
if (call.name !== 'this' || call.receiver) continue;
|
|
755
|
-
|
|
756
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
757
|
-
if (caller.callerName == null) continue;
|
|
758
|
-
|
|
759
|
-
const scopedKey = `${caller.callerName}::this`;
|
|
760
|
-
if (!ptsMap.has(scopedKey)) continue;
|
|
761
|
-
|
|
762
|
-
for (const alias of resolveViaPointsTo(scopedKey, ptsMap)) {
|
|
763
|
-
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
|
|
764
|
-
lookup,
|
|
765
|
-
{ name: alias },
|
|
766
|
-
relPath,
|
|
767
|
-
importedNames,
|
|
768
|
-
typeMap as Map<string, unknown>,
|
|
769
|
-
);
|
|
770
|
-
for (const t of aliasTargets) {
|
|
771
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
772
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
773
|
-
const conf =
|
|
774
|
-
computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
775
|
-
if (conf > 0) {
|
|
776
|
-
seenByPair.add(edgeKey);
|
|
777
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
/**
|
|
787
|
-
* Phase 8.3f post-pass for the native call-edge path.
|
|
788
|
-
*
|
|
789
|
-
* The native Rust engine builds call edges without knowledge of
|
|
790
|
-
* objectRestParamBindings, so `rest.method()` calls inside functions with
|
|
791
|
-
* object-destructuring rest parameters are not resolved via the typeMap chain.
|
|
792
|
-
* The Rust engine already resolves same-file and directly-imported callees
|
|
793
|
-
* (via steps 1–2 of its resolution logic), so this post-pass only adds edges
|
|
794
|
-
* that require the typeMap-chain path:
|
|
795
|
-
* typeMap[restName] → argName → typeMap[argName.method] → target
|
|
796
|
-
*
|
|
797
|
-
* Mirrors the seeding in buildCallEdgesJS (Phase 8.3f) to ensure both engine
|
|
798
|
-
* paths produce identical results for receiver-typed rest-param calls.
|
|
799
|
-
*/
|
|
800
|
-
function buildObjectRestParamPostPass(
|
|
801
|
-
ctx: PipelineContext,
|
|
802
|
-
getNodeIdStmt: NodeIdStmt,
|
|
803
|
-
allEdgeRows: EdgeRowTuple[],
|
|
804
|
-
sharedLookup?: CallNodeLookup,
|
|
805
|
-
): void {
|
|
806
|
-
const filesWithRestBindings = [...ctx.fileSymbols].filter(
|
|
807
|
-
([, symbols]) =>
|
|
808
|
-
symbols.objectRestParamBindings &&
|
|
809
|
-
symbols.objectRestParamBindings.length > 0 &&
|
|
810
|
-
symbols.paramBindings &&
|
|
811
|
-
symbols.paramBindings.length > 0,
|
|
812
|
-
);
|
|
813
|
-
if (filesWithRestBindings.length === 0) return;
|
|
814
|
-
|
|
815
|
-
const seenByPair = new Set<string>();
|
|
816
|
-
for (const [srcId, tgtId] of allEdgeRows) {
|
|
817
|
-
seenByPair.add(`${srcId}|${tgtId}`);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
const { barrelOnlyFiles, rootDir } = ctx;
|
|
821
|
-
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
|
|
822
|
-
|
|
823
|
-
for (const [relPath, symbols] of filesWithRestBindings) {
|
|
824
|
-
if (barrelOnlyFiles.has(relPath)) continue;
|
|
825
|
-
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
826
|
-
if (!fileNodeRow) continue;
|
|
827
|
-
|
|
828
|
-
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
|
|
829
|
-
const typeMap: Map<string, TypeMapEntry | string> = new Map(
|
|
830
|
-
symbols.typeMap instanceof Map ? symbols.typeMap : [],
|
|
831
|
-
);
|
|
832
|
-
|
|
833
|
-
// Seed typeMap[callee::restName] = { type: argName } for each matching pair.
|
|
834
|
-
// Mirrors the seeding in buildCallEdgesJS Phase 8.3f. Keys are scoped by
|
|
835
|
-
// callee so two functions with the same rest-param name (e.g. `...rest`) in
|
|
836
|
-
// the same file don't collide (#1358).
|
|
837
|
-
// When only one callee uses a given rest name, also seed the unscoped key
|
|
838
|
-
// as a null-callerName fallback so edges aren't silently dropped if
|
|
839
|
-
// findCaller can't identify the enclosing function (#1358).
|
|
840
|
-
const restNameCallees = new Map<string, Set<string>>();
|
|
841
|
-
for (const orpb of symbols.objectRestParamBindings!) {
|
|
842
|
-
if (!restNameCallees.has(orpb.restName)) restNameCallees.set(orpb.restName, new Set());
|
|
843
|
-
restNameCallees.get(orpb.restName)!.add(orpb.callee);
|
|
844
|
-
}
|
|
845
|
-
const restNames = new Set<string>();
|
|
846
|
-
for (const orpb of symbols.objectRestParamBindings!) {
|
|
847
|
-
for (const pb of symbols.paramBindings!) {
|
|
848
|
-
if (pb.callee === orpb.callee && pb.argIndex === orpb.argIndex) {
|
|
849
|
-
const scopedKey = `${orpb.callee}::${orpb.restName}`;
|
|
850
|
-
if (!typeMap.has(scopedKey)) {
|
|
851
|
-
typeMap.set(scopedKey, { type: pb.argName, confidence: 0.65 });
|
|
852
|
-
if (restNameCallees.get(orpb.restName)!.size === 1 && !typeMap.has(orpb.restName)) {
|
|
853
|
-
typeMap.set(orpb.restName, { type: pb.argName, confidence: 0.65 });
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
// restNames tracks every rest-parameter name found, regardless of whether the
|
|
857
|
-
// scoped key was already in typeMap. This ensures the post-pass (below) processes
|
|
858
|
-
// all calls whose receiver matches a known rest binding — not just those whose
|
|
859
|
-
// typeMap entry was seeded in this iteration.
|
|
860
|
-
restNames.add(orpb.restName);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
if (restNames.size === 0) continue;
|
|
865
|
-
|
|
866
|
-
for (const call of symbols.calls) {
|
|
867
|
-
// Only process calls whose receiver is a known rest-binding name.
|
|
868
|
-
if (!call.receiver || !restNames.has(call.receiver)) continue;
|
|
869
|
-
|
|
870
|
-
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
871
|
-
|
|
872
|
-
// Resolve with the enriched typeMap. callerName is passed so
|
|
873
|
-
// resolveByMethodOrGlobal can look up the scoped key callee::restName (#1358).
|
|
874
|
-
// seenByPair deduplicates edges the native engine already emitted.
|
|
875
|
-
const { targets, importedFrom } = resolveCallTargets(
|
|
876
|
-
lookup,
|
|
877
|
-
call,
|
|
878
|
-
relPath,
|
|
879
|
-
importedNames,
|
|
880
|
-
typeMap as Map<string, unknown>,
|
|
881
|
-
caller.callerName,
|
|
882
|
-
);
|
|
883
|
-
for (const t of targets) {
|
|
884
|
-
const edgeKey = `${caller.id}|${t.id}`;
|
|
885
|
-
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
886
|
-
const conf =
|
|
887
|
-
computeConfidence(relPath, t.file, importedFrom ?? null) - PROPAGATION_HOP_PENALTY;
|
|
888
|
-
if (conf > 0) {
|
|
889
|
-
seenByPair.add(edgeKey);
|
|
890
|
-
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
|
|
898
584
|
/**
|
|
899
585
|
* Object.defineProperty accessor post-pass for the native call-edge path.
|
|
900
586
|
*
|
|
@@ -988,11 +674,11 @@ function buildDefinePropertyPostPass(
|
|
|
988
674
|
*
|
|
989
675
|
* The native Rust engine has no knowledge of the CHA context, so `this.method()`
|
|
990
676
|
* calls and interface method dispatches are not expanded to their concrete
|
|
991
|
-
* implementations. This JS post-pass runs after the native edges
|
|
992
|
-
*
|
|
677
|
+
* implementations. This JS post-pass runs after the native edges and adds only
|
|
678
|
+
* the CHA-resolved edges that the native engine missed.
|
|
993
679
|
*
|
|
994
|
-
*
|
|
995
|
-
*
|
|
680
|
+
* Seeds seenByPair from the current allEdgeRows snapshot to avoid duplicating
|
|
681
|
+
* edges the native engine already produced.
|
|
996
682
|
*/
|
|
997
683
|
function buildChaPostPass(
|
|
998
684
|
ctx: PipelineContext,
|
|
@@ -1026,6 +712,7 @@ function buildChaPostPass(
|
|
|
1026
712
|
|
|
1027
713
|
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
1028
714
|
let chaTargets: ReadonlyArray<{ id: number; file: string }> = [];
|
|
715
|
+
let isTypedReceiverDispatch = false;
|
|
1029
716
|
|
|
1030
717
|
if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
|
|
1031
718
|
chaTargets = resolveThisDispatch(
|
|
@@ -1034,6 +721,7 @@ function buildChaPostPass(
|
|
|
1034
721
|
call.receiver,
|
|
1035
722
|
chaCtx,
|
|
1036
723
|
lookup,
|
|
724
|
+
relPath,
|
|
1037
725
|
);
|
|
1038
726
|
} else {
|
|
1039
727
|
const typeEntry = typeMap.get(call.receiver);
|
|
@@ -1044,16 +732,26 @@ function buildChaPostPass(
|
|
|
1044
732
|
: null;
|
|
1045
733
|
if (typeName) {
|
|
1046
734
|
chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
|
|
735
|
+
isTypedReceiverDispatch = true;
|
|
1047
736
|
}
|
|
1048
737
|
}
|
|
1049
738
|
|
|
1050
739
|
for (const t of chaTargets) {
|
|
1051
740
|
const edgeKey = `${caller.id}|${t.id}`;
|
|
1052
741
|
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
|
|
1053
|
-
|
|
742
|
+
// Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
|
|
743
|
+
// — file proximity is not meaningful for virtual dispatch confidence.
|
|
744
|
+
// this/super dispatch keeps computeConfidence-based proximity scoring to
|
|
745
|
+
// match runPostNativeThisDispatch (native-orchestrator.ts).
|
|
746
|
+
const conf = isTypedReceiverDispatch
|
|
747
|
+
? CHA_TYPED_DISPATCH_CONFIDENCE
|
|
748
|
+
: computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
|
|
1054
749
|
if (conf > 0) {
|
|
1055
750
|
seenByPair.add(edgeKey);
|
|
1056
|
-
|
|
751
|
+
// Tag super-dispatch edges distinctly so runChaPostPass can exclude them
|
|
752
|
+
// from further CHA expansion (super calls are not virtual dispatch).
|
|
753
|
+
const technique = call.receiver === 'super' ? 'super-dispatch' : 'cha';
|
|
754
|
+
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, technique]);
|
|
1057
755
|
}
|
|
1058
756
|
}
|
|
1059
757
|
}
|
|
@@ -1317,7 +1015,6 @@ function buildFileCallEdges(
|
|
|
1317
1015
|
// bind/alias entries, not for every locally-defined function or import that
|
|
1318
1016
|
// buildPointsToMap seeds with a self-pointing entry.
|
|
1319
1017
|
const fnRefBindingLhs = new Set(symbols.fnRefBindings?.map((b) => b.lhs) ?? []);
|
|
1320
|
-
|
|
1321
1018
|
for (const call of symbols.calls) {
|
|
1322
1019
|
if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver)) continue;
|
|
1323
1020
|
|
|
@@ -1424,6 +1121,19 @@ function buildFileCallEdges(
|
|
|
1424
1121
|
}
|
|
1425
1122
|
}
|
|
1426
1123
|
|
|
1124
|
+
// Sort targets by confidence descending before emitting edges.
|
|
1125
|
+
// For multi-target calls with duplicate (source_id, target_id) pairs the
|
|
1126
|
+
// stored confidence depends on which duplicate is processed last — sorting
|
|
1127
|
+
// here guarantees the highest-confidence target wins on dedup, matching the
|
|
1128
|
+
// native engine's sort_targets_by_confidence call in build_edges.rs.
|
|
1129
|
+
if (targets.length > 1) {
|
|
1130
|
+
targets = [...targets].sort(
|
|
1131
|
+
(a, b) =>
|
|
1132
|
+
computeConfidence(relPath, b.file, importedFrom ?? null) -
|
|
1133
|
+
computeConfidence(relPath, a.file, importedFrom ?? null),
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1427
1137
|
for (const t of targets) {
|
|
1428
1138
|
const edgeKey = `${caller.id}|${t.id}`;
|
|
1429
1139
|
if (t.id !== caller.id) {
|
|
@@ -1505,7 +1215,15 @@ function buildFileCallEdges(
|
|
|
1505
1215
|
importedNames,
|
|
1506
1216
|
typeMap as Map<string, unknown>,
|
|
1507
1217
|
);
|
|
1508
|
-
|
|
1218
|
+
const sortedAliasTargets =
|
|
1219
|
+
aliasTargets.length > 1
|
|
1220
|
+
? [...aliasTargets].sort(
|
|
1221
|
+
(a, b) =>
|
|
1222
|
+
computeConfidence(relPath, b.file, aliasFrom ?? null) -
|
|
1223
|
+
computeConfidence(relPath, a.file, aliasFrom ?? null),
|
|
1224
|
+
)
|
|
1225
|
+
: aliasTargets;
|
|
1226
|
+
for (const t of sortedAliasTargets) {
|
|
1509
1227
|
const edgeKey = `${caller.id}|${t.id}`;
|
|
1510
1228
|
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1511
1229
|
const conf =
|
|
@@ -1541,7 +1259,15 @@ function buildFileCallEdges(
|
|
|
1541
1259
|
importedNames,
|
|
1542
1260
|
typeMap as Map<string, unknown>,
|
|
1543
1261
|
);
|
|
1544
|
-
|
|
1262
|
+
const sortedAliasTargets =
|
|
1263
|
+
aliasTargets.length > 1
|
|
1264
|
+
? [...aliasTargets].sort(
|
|
1265
|
+
(a, b) =>
|
|
1266
|
+
computeConfidence(relPath, b.file, aliasFrom ?? null) -
|
|
1267
|
+
computeConfidence(relPath, a.file, aliasFrom ?? null),
|
|
1268
|
+
)
|
|
1269
|
+
: aliasTargets;
|
|
1270
|
+
for (const t of sortedAliasTargets) {
|
|
1545
1271
|
const edgeKey = `${caller.id}|${t.id}`;
|
|
1546
1272
|
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1547
1273
|
const conf =
|
|
@@ -1570,6 +1296,7 @@ function buildFileCallEdges(
|
|
|
1570
1296
|
relPath,
|
|
1571
1297
|
typeMap as Map<string, unknown>,
|
|
1572
1298
|
seenCallEdges,
|
|
1299
|
+
importedNames,
|
|
1573
1300
|
);
|
|
1574
1301
|
if (recv) {
|
|
1575
1302
|
allEdgeRows.push([recv.callerId, recv.receiverId, 'receiver', recv.confidence, 0, null]);
|
|
@@ -1582,6 +1309,7 @@ function buildFileCallEdges(
|
|
|
1582
1309
|
// For typed receiver calls: expand to all instantiated concrete implementations.
|
|
1583
1310
|
if (chaCtx && call.receiver) {
|
|
1584
1311
|
let chaTargets: ReadonlyArray<{ id: number; file: string }> = [];
|
|
1312
|
+
let isTypedReceiverDispatch = false;
|
|
1585
1313
|
if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super') {
|
|
1586
1314
|
chaTargets = resolveThisDispatch(
|
|
1587
1315
|
call.name,
|
|
@@ -1589,6 +1317,7 @@ function buildFileCallEdges(
|
|
|
1589
1317
|
call.receiver,
|
|
1590
1318
|
chaCtx,
|
|
1591
1319
|
lookup,
|
|
1320
|
+
relPath,
|
|
1592
1321
|
);
|
|
1593
1322
|
} else if (!BUILTIN_RECEIVERS.has(call.receiver)) {
|
|
1594
1323
|
const typeEntry = typeMap.get(call.receiver);
|
|
@@ -1599,12 +1328,19 @@ function buildFileCallEdges(
|
|
|
1599
1328
|
: null;
|
|
1600
1329
|
if (typeName) {
|
|
1601
1330
|
chaTargets = resolveChaTargets(typeName, call.name, chaCtx, lookup);
|
|
1331
|
+
isTypedReceiverDispatch = true;
|
|
1602
1332
|
}
|
|
1603
1333
|
}
|
|
1604
1334
|
for (const t of chaTargets) {
|
|
1605
1335
|
const edgeKey = `${caller.id}|${t.id}`;
|
|
1606
1336
|
if (t.id !== caller.id && !seenCallEdges.has(edgeKey) && !ptsEdgeRows.has(edgeKey)) {
|
|
1607
|
-
|
|
1337
|
+
// Typed-receiver (interface/CHA) dispatch: use CHA_TYPED_DISPATCH_CONFIDENCE
|
|
1338
|
+
// — file proximity is not meaningful for virtual dispatch confidence.
|
|
1339
|
+
// this/super dispatch keeps computeConfidence-based proximity scoring to
|
|
1340
|
+
// match runPostNativeThisDispatch (native-orchestrator.ts).
|
|
1341
|
+
const conf = isTypedReceiverDispatch
|
|
1342
|
+
? CHA_TYPED_DISPATCH_CONFIDENCE
|
|
1343
|
+
: computeConfidence(relPath, t.file, null) - CHA_DISPATCH_PENALTY;
|
|
1608
1344
|
if (conf > 0) {
|
|
1609
1345
|
seenCallEdges.add(edgeKey);
|
|
1610
1346
|
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'cha']);
|
|
@@ -1775,7 +1511,7 @@ function reconnectReverseDepEdges(ctx: PipelineContext): void {
|
|
|
1775
1511
|
* their import targets. Falls back to loading ALL nodes for full builds or
|
|
1776
1512
|
* larger incremental changes.
|
|
1777
1513
|
*/
|
|
1778
|
-
const NODE_KIND_FILTER_SQL = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant')`;
|
|
1514
|
+
const NODE_KIND_FILTER_SQL = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant','variable')`;
|
|
1779
1515
|
|
|
1780
1516
|
function loadNodes(ctx: PipelineContext): { rows: QueryNodeRow[]; scoped: boolean } {
|
|
1781
1517
|
const { db, fileSymbols, isFullBuild, batchResolved } = ctx;
|
|
@@ -1858,7 +1594,16 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
|
|
|
1858
1594
|
// Enrich typeMap for .ts/.tsx files using the TypeScript compiler API.
|
|
1859
1595
|
// Runs before call-edge construction so the accurate types are available
|
|
1860
1596
|
// for method-call resolution. Gated on config so users can opt out.
|
|
1861
|
-
|
|
1597
|
+
//
|
|
1598
|
+
// Skip for small incremental builds: TypeScript program creation requires
|
|
1599
|
+
// loading the entire tsconfig file list (~700ms startup on the codegraph
|
|
1600
|
+
// corpus), which dominates the 1-file rebuild time. Native engine bypasses
|
|
1601
|
+
// this entirely via the Rust orchestrator; WASM/JS engines need this gate
|
|
1602
|
+
// to match native's effective behaviour on tiny incremental changes.
|
|
1603
|
+
// Mirrors the smallFilesThreshold gates for nativeDb and native call-edges.
|
|
1604
|
+
const isSmallIncremental =
|
|
1605
|
+
!ctx.isFullBuild && ctx.fileSymbols.size <= ctx.config.build.smallFilesThreshold;
|
|
1606
|
+
if (ctx.config.build.typescriptResolver && !isSmallIncremental) {
|
|
1862
1607
|
await enrichTypeMapWithTsc(ctx.rootDir, ctx.fileSymbols);
|
|
1863
1608
|
}
|
|
1864
1609
|
|
|
@@ -1921,26 +1666,12 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
|
|
|
1921
1666
|
(ctx.isFullBuild || ctx.fileSymbols.size > ctx.config.build.smallFilesThreshold);
|
|
1922
1667
|
if (useNativeCallEdges) {
|
|
1923
1668
|
buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodesBefore, native!);
|
|
1924
|
-
//
|
|
1925
|
-
//
|
|
1669
|
+
// The native engine receives all pts bindings (paramBindings,
|
|
1670
|
+
// fnRefBindings, thisCallBindings, objectRestParamBindings, …) through
|
|
1671
|
+
// NativeFileEntry and runs the same points-to solver as the JS path, so
|
|
1672
|
+
// no pts post-passes are needed here. Only capabilities that remain
|
|
1673
|
+
// JS-only run as post-passes below.
|
|
1926
1674
|
const sharedLookup = makeContextLookup(ctx, getNodeIdStmt);
|
|
1927
|
-
// Phase 8.3c post-pass: augment native call edges with parameter-flow pts
|
|
1928
|
-
// edges. The native Rust engine has no knowledge of paramBindings, so any
|
|
1929
|
-
// `fn()` call inside a higher-order function would be missed. This JS pass
|
|
1930
|
-
// runs on top of the native edges and adds only the pts-resolved edges that
|
|
1931
|
-
// the native engine could not produce.
|
|
1932
|
-
buildParamFlowPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1933
|
-
// bind/alias post-pass: augment native call edges with fnRefBindings-seeded
|
|
1934
|
-
// pts edges. The native Rust engine has no knowledge of JS fnRefBindings
|
|
1935
|
-
// (e.g. `const f = fn.bind(ctx)`), so calls to bind-created aliases are
|
|
1936
|
-
// not resolved to their original function on the native path.
|
|
1937
|
-
buildFnRefBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1938
|
-
// this-rebinding post-pass: resolve `this()` calls inside functions that
|
|
1939
|
-
// were invoked via `.call(namedCtx, ...)` / `.apply(namedCtx, ...)`.
|
|
1940
|
-
buildThisCallBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1941
|
-
// Phase 8.3f post-pass: augment native call edges with object rest-param
|
|
1942
|
-
// receiver resolution — typeMap[restName] → argName → typeMap[argName.method].
|
|
1943
|
-
buildObjectRestParamPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|
|
1944
1675
|
// Object.defineProperty accessor post-pass: resolve this-dispatch inside
|
|
1945
1676
|
// getter/setter functions registered via Object.defineProperty.
|
|
1946
1677
|
buildDefinePropertyPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
|