gitnexus 1.6.3-rc.8 → 1.6.3
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 +21 -5
- package/dist/_shared/graph/types.d.ts +16 -0
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +20 -2
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +11 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/def-index.js +2 -2
- package/dist/_shared/scope-resolution/def-index.js.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
- package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
- package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/parsed-file.js +54 -0
- package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
- package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
- package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/position-index.js +2 -2
- package/dist/_shared/scope-resolution/position-index.js.map +1 -1
- package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
- package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/reference-site.js +24 -0
- package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts +27 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js +30 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts +69 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.js +44 -0
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts +56 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.js +150 -0
- package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts +26 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js +31 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +81 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js +332 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts +33 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js +56 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts +36 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js +32 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts +43 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js +60 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js.map +1 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js +3 -2
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
- package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +11 -0
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.js +35 -4
- package/dist/cli/analyze.d.ts +27 -0
- package/dist/cli/analyze.js +31 -1
- package/dist/cli/clean.js +19 -1
- package/dist/cli/group.js +73 -0
- package/dist/cli/index-repo.js +8 -1
- package/dist/cli/index.js +26 -1
- package/dist/cli/list.js +11 -1
- package/dist/cli/remove.d.ts +30 -0
- package/dist/cli/remove.js +99 -0
- package/dist/cli/setup.js +185 -57
- package/dist/cli/tool.d.ts +5 -0
- package/dist/cli/tool.js +42 -0
- package/dist/config/ignore-service.d.ts +9 -0
- package/dist/config/ignore-service.js +80 -13
- package/dist/core/embedding-mode.d.ts +30 -0
- package/dist/core/embedding-mode.js +30 -0
- package/dist/core/embeddings/ast-utils.js +22 -22
- package/dist/core/embeddings/chunker.js +30 -25
- package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
- package/dist/core/embeddings/embedding-pipeline.js +15 -6
- package/dist/core/embeddings/text-generator.d.ts +1 -1
- package/dist/core/embeddings/text-generator.js +33 -24
- package/dist/core/embeddings/types.d.ts +43 -1
- package/dist/core/embeddings/types.js +101 -29
- package/dist/core/git-staleness.d.ts +18 -0
- package/dist/core/git-staleness.js +108 -0
- package/dist/core/graph/graph.js +115 -20
- package/dist/core/graph/types.d.ts +12 -1
- package/dist/core/group/config-parser.d.ts +4 -0
- package/dist/core/group/config-parser.js +18 -1
- package/dist/core/group/cross-impact.d.ts +41 -0
- package/dist/core/group/cross-impact.js +441 -0
- package/dist/core/group/extractors/http-patterns/php.js +126 -18
- package/dist/core/group/group-path-utils.d.ts +17 -0
- package/dist/core/group/group-path-utils.js +40 -0
- package/dist/core/group/resolve-at-member.d.ts +10 -0
- package/dist/core/group/resolve-at-member.js +31 -0
- package/dist/core/group/service.d.ts +9 -0
- package/dist/core/group/service.js +259 -25
- package/dist/core/group/types.d.ts +30 -0
- package/dist/core/ingestion/ast-cache.d.ts +16 -1
- package/dist/core/ingestion/ast-cache.js +14 -2
- package/dist/core/ingestion/call-processor.js +9 -0
- package/dist/core/ingestion/emit-references.d.ts +88 -0
- package/dist/core/ingestion/emit-references.js +229 -0
- package/dist/core/ingestion/filesystem-walker.js +6 -4
- package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
- package/dist/core/ingestion/finalize-orchestrator.js +139 -0
- package/dist/core/ingestion/framework-detection.js +6 -2
- package/dist/core/ingestion/import-processor.js +4 -0
- package/dist/core/ingestion/import-resolvers/python.js +9 -6
- package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
- package/dist/core/ingestion/import-target-adapter.js +95 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -33
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
- package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
- package/dist/core/ingestion/languages/csharp/arity.js +37 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
- package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/captures.js +249 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
- package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
- package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
- package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
- package/dist/core/ingestion/languages/csharp/index.js +82 -0
- package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
- package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
- package/dist/core/ingestion/languages/csharp/query.js +515 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
- package/dist/core/ingestion/languages/csharp.js +14 -0
- package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
- package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
- package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
- package/dist/core/ingestion/languages/python/arity.js +38 -0
- package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
- package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
- package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/python/captures.js +106 -0
- package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
- package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
- package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
- package/dist/core/ingestion/languages/python/import-target.js +99 -0
- package/dist/core/ingestion/languages/python/index.d.ts +80 -0
- package/dist/core/ingestion/languages/python/index.js +80 -0
- package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/python/interpret.js +191 -0
- package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
- package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
- package/dist/core/ingestion/languages/python/query.d.ts +9 -0
- package/dist/core/ingestion/languages/python/query.js +267 -0
- package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
- package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
- package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
- package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
- package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
- package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
- package/dist/core/ingestion/languages/python.js +14 -0
- package/dist/core/ingestion/model/method-registry.d.ts +9 -0
- package/dist/core/ingestion/model/method-registry.js +4 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
- package/dist/core/ingestion/model/semantic-model.js +55 -0
- package/dist/core/ingestion/mro-processor.js +38 -22
- package/dist/core/ingestion/parsing-processor.d.ts +18 -1
- package/dist/core/ingestion/parsing-processor.js +45 -11
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
- package/dist/core/ingestion/pipeline.js +2 -1
- package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
- package/dist/core/ingestion/registry-primary-flag.js +111 -0
- package/dist/core/ingestion/resolve-references.d.ts +63 -0
- package/dist/core/ingestion/resolve-references.js +175 -0
- package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
- package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
- package/dist/core/ingestion/scope-extractor.d.ts +86 -0
- package/dist/core/ingestion/scope-extractor.js +758 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
- package/dist/core/ingestion/shadow-harness.d.ts +113 -0
- package/dist/core/ingestion/shadow-harness.js +148 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
- package/dist/core/ingestion/utils/ast-helpers.js +70 -0
- package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
- package/dist/core/ingestion/utils/max-file-size.js +52 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
- package/dist/core/ingestion/workers/parse-worker.js +57 -21
- package/dist/core/lbug/lbug-adapter.d.ts +22 -2
- package/dist/core/lbug/lbug-adapter.js +58 -14
- package/dist/core/lbug/pool-adapter.d.ts +17 -0
- package/dist/core/lbug/pool-adapter.js +24 -14
- package/dist/core/run-analyze.d.ts +32 -0
- package/dist/core/run-analyze.js +74 -19
- package/dist/core/search/bm25-index.d.ts +18 -0
- package/dist/core/search/bm25-index.js +125 -12
- package/dist/core/tree-sitter/parser-loader.js +6 -1
- package/dist/mcp/local/local-backend.d.ts +67 -3
- package/dist/mcp/local/local-backend.js +296 -34
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +100 -17
- package/dist/mcp/tools.d.ts +4 -1
- package/dist/mcp/tools.js +75 -54
- package/dist/server/api.js +6 -2
- package/dist/storage/git.d.ts +49 -0
- package/dist/storage/git.js +111 -0
- package/dist/storage/repo-manager.d.ts +246 -1
- package/dist/storage/repo-manager.js +391 -9
- package/package.json +7 -6
- package/scripts/bench-scope-resolution.ts +134 -0
- package/scripts/ci-list-migrated-languages.ts +24 -0
- package/skills/gitnexus-cli.md +1 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic MRO (method-resolution-order) builder.
|
|
3
|
+
*
|
|
4
|
+
* Walks the graph's `EXTENDS` edges to recover an inheritance map,
|
|
5
|
+
* then asks the per-language `LinearizeStrategy` to order each class's
|
|
6
|
+
* ancestors. Returns `Map<classDefId, ancestorDefId[]>` ready to plug
|
|
7
|
+
* into `MethodDispatchIndex` via `buildPopulatedMethodDispatch`.
|
|
8
|
+
*
|
|
9
|
+
* **Why a strategy hook:** linearization differs across languages.
|
|
10
|
+
* - Python (depth-first first-seen, single inheritance): trivially
|
|
11
|
+
* correct; multi-inheritance falls back to BFS dedup. Real C3
|
|
12
|
+
* would handle diamond hierarchies — defer until we hit one.
|
|
13
|
+
* - Java (single-inheritance only): walk one parent.
|
|
14
|
+
* - C++ (multiple inheritance): C3-like or BFS depending on how
|
|
15
|
+
* strict the consumer needs to be.
|
|
16
|
+
* - Languages without inheritance (COBOL): return empty list.
|
|
17
|
+
*
|
|
18
|
+
* The strategy receives the FULL ancestry context (`directParents` +
|
|
19
|
+
* `parentsByDefId`) so C3 implementations have what they need.
|
|
20
|
+
*/
|
|
21
|
+
import { resolveDefGraphId } from '../graph-bridge/ids.js';
|
|
22
|
+
/**
|
|
23
|
+
* Build an MRO map keyed by scope-resolution Class `DefId`.
|
|
24
|
+
*
|
|
25
|
+
* Steps:
|
|
26
|
+
* 1. Collect EXTENDS edges from the graph → `parentsByGraphId`.
|
|
27
|
+
* 2. Collect Class defs from `parsedFiles` and translate to graph
|
|
28
|
+
* ids via `nodeLookup` → `defIdByGraphId` (the bridge between
|
|
29
|
+
* scope-resolution DefId and the legacy graph node id).
|
|
30
|
+
* 3. For each Class def, ask `linearize` for its ancestor order.
|
|
31
|
+
*/
|
|
32
|
+
export function buildMro(graph, parsedFiles, nodeLookup, linearize) {
|
|
33
|
+
// Step 1: parentsByGraphId — typed iterator skips the per-edge type
|
|
34
|
+
// check and the millions of CALLS/ACCESSES/IMPORTS/DEFINES edges
|
|
35
|
+
// that aren't relevant to MRO.
|
|
36
|
+
const parentsByGraphId = new Map();
|
|
37
|
+
for (const rel of graph.iterRelationshipsByType('EXTENDS')) {
|
|
38
|
+
let list = parentsByGraphId.get(rel.sourceId);
|
|
39
|
+
if (list === undefined) {
|
|
40
|
+
list = [];
|
|
41
|
+
parentsByGraphId.set(rel.sourceId, list);
|
|
42
|
+
}
|
|
43
|
+
list.push(rel.targetId);
|
|
44
|
+
}
|
|
45
|
+
// Step 2: defIdByGraphId — translate graph ids to scope-resolution DefIds.
|
|
46
|
+
const defIdByGraphId = new Map();
|
|
47
|
+
for (const parsed of parsedFiles) {
|
|
48
|
+
for (const def of parsed.localDefs) {
|
|
49
|
+
if (def.type !== 'Class')
|
|
50
|
+
continue;
|
|
51
|
+
const graphId = resolveDefGraphId(parsed.filePath, def, nodeLookup);
|
|
52
|
+
if (graphId !== undefined)
|
|
53
|
+
defIdByGraphId.set(graphId, def.nodeId);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Step 2b: invert parentsByGraphId into parentsByDefId — the
|
|
57
|
+
// strategy works in DefId space.
|
|
58
|
+
const parentsByDefId = new Map();
|
|
59
|
+
for (const [childGraphId, parents] of parentsByGraphId) {
|
|
60
|
+
const childDefId = defIdByGraphId.get(childGraphId);
|
|
61
|
+
if (childDefId === undefined)
|
|
62
|
+
continue;
|
|
63
|
+
const parentDefIds = [];
|
|
64
|
+
for (const p of parents) {
|
|
65
|
+
const pd = defIdByGraphId.get(p);
|
|
66
|
+
if (pd !== undefined)
|
|
67
|
+
parentDefIds.push(pd);
|
|
68
|
+
}
|
|
69
|
+
parentsByDefId.set(childDefId, parentDefIds);
|
|
70
|
+
}
|
|
71
|
+
// Step 3: linearize per class.
|
|
72
|
+
const mroByDefId = new Map();
|
|
73
|
+
for (const defId of defIdByGraphId.values()) {
|
|
74
|
+
const directParents = parentsByDefId.get(defId) ?? [];
|
|
75
|
+
mroByDefId.set(defId, linearize(defId, directParents, parentsByDefId));
|
|
76
|
+
}
|
|
77
|
+
return mroByDefId;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Default linearization: depth-first BFS-with-visited, first-seen
|
|
81
|
+
* wins. Correct for single-inheritance languages and for Python's
|
|
82
|
+
* simplified MRO. Multi-inheritance diamond hierarchies need a real
|
|
83
|
+
* C3 implementation; per-language overrides land here.
|
|
84
|
+
*/
|
|
85
|
+
export const defaultLinearize = (_classDefId, directParents, parentsByDefId) => {
|
|
86
|
+
const ancestors = [];
|
|
87
|
+
const visited = new Set();
|
|
88
|
+
const queue = [...directParents];
|
|
89
|
+
while (queue.length > 0) {
|
|
90
|
+
const cur = queue.shift();
|
|
91
|
+
if (visited.has(cur))
|
|
92
|
+
continue;
|
|
93
|
+
visited.add(cur);
|
|
94
|
+
ancestors.push(cur);
|
|
95
|
+
for (const p of parentsByDefId.get(cur) ?? [])
|
|
96
|
+
queue.push(p);
|
|
97
|
+
}
|
|
98
|
+
return ancestors;
|
|
99
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overload narrowing — pick candidates from a list of same-named
|
|
3
|
+
* method / function overloads using the call-site's arity and
|
|
4
|
+
* argument-type signals.
|
|
5
|
+
*
|
|
6
|
+
* Used by both `receiver-bound-calls.ts::pickOverload` (explicit
|
|
7
|
+
* receiver member call) and `free-call-fallback.ts::pickImplicitThisOverload`
|
|
8
|
+
* (implicit `this` free-call inside a class-like body). Shared to keep
|
|
9
|
+
* narrowing semantics in lockstep across the two sites.
|
|
10
|
+
*
|
|
11
|
+
* Semantics (first-wins; callers take `result[0]`):
|
|
12
|
+
* 1. If `argCount` is undefined, arity is a pass-through.
|
|
13
|
+
* 2. Exact-required-match wins over variadic. Variadic is detected
|
|
14
|
+
* via a `parameterTypes` entry equal to `'params'` or starting
|
|
15
|
+
* with `'params '` (C# `params` / variadic marker).
|
|
16
|
+
* 3. If the arity filter empties the set, fall back to the full
|
|
17
|
+
* overload list rather than returning nothing — the caller still
|
|
18
|
+
* needs a best-effort candidate.
|
|
19
|
+
* 4. If `argTypes` is present, filter further by per-slot type
|
|
20
|
+
* equality. An empty string in `argTypes[i]` means "unknown" and
|
|
21
|
+
* counts as a match. Mismatches disqualify. A non-empty typed
|
|
22
|
+
* result wins; otherwise return the arity-filtered candidates.
|
|
23
|
+
* 5. Empty input returns empty output.
|
|
24
|
+
*/
|
|
25
|
+
import type { SymbolDefinition } from '../../../../_shared/index.js';
|
|
26
|
+
export declare function narrowOverloadCandidates(overloads: readonly SymbolDefinition[], argCount: number | undefined, argTypes: readonly string[] | undefined): readonly SymbolDefinition[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overload narrowing — pick candidates from a list of same-named
|
|
3
|
+
* method / function overloads using the call-site's arity and
|
|
4
|
+
* argument-type signals.
|
|
5
|
+
*
|
|
6
|
+
* Used by both `receiver-bound-calls.ts::pickOverload` (explicit
|
|
7
|
+
* receiver member call) and `free-call-fallback.ts::pickImplicitThisOverload`
|
|
8
|
+
* (implicit `this` free-call inside a class-like body). Shared to keep
|
|
9
|
+
* narrowing semantics in lockstep across the two sites.
|
|
10
|
+
*
|
|
11
|
+
* Semantics (first-wins; callers take `result[0]`):
|
|
12
|
+
* 1. If `argCount` is undefined, arity is a pass-through.
|
|
13
|
+
* 2. Exact-required-match wins over variadic. Variadic is detected
|
|
14
|
+
* via a `parameterTypes` entry equal to `'params'` or starting
|
|
15
|
+
* with `'params '` (C# `params` / variadic marker).
|
|
16
|
+
* 3. If the arity filter empties the set, fall back to the full
|
|
17
|
+
* overload list rather than returning nothing — the caller still
|
|
18
|
+
* needs a best-effort candidate.
|
|
19
|
+
* 4. If `argTypes` is present, filter further by per-slot type
|
|
20
|
+
* equality. An empty string in `argTypes[i]` means "unknown" and
|
|
21
|
+
* counts as a match. Mismatches disqualify. A non-empty typed
|
|
22
|
+
* result wins; otherwise return the arity-filtered candidates.
|
|
23
|
+
* 5. Empty input returns empty output.
|
|
24
|
+
*/
|
|
25
|
+
export function narrowOverloadCandidates(overloads, argCount, argTypes) {
|
|
26
|
+
if (overloads.length === 0)
|
|
27
|
+
return [];
|
|
28
|
+
const arityMatches = argCount === undefined
|
|
29
|
+
? overloads
|
|
30
|
+
: overloads.filter((d) => {
|
|
31
|
+
const max = d.parameterCount;
|
|
32
|
+
const min = d.requiredParameterCount;
|
|
33
|
+
if (max !== undefined && argCount > max) {
|
|
34
|
+
const variadic = d.parameterTypes !== undefined &&
|
|
35
|
+
d.parameterTypes.some((t) => t === 'params' || t.startsWith('params '));
|
|
36
|
+
if (!variadic)
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (min !== undefined && argCount < min)
|
|
40
|
+
return false;
|
|
41
|
+
return true;
|
|
42
|
+
});
|
|
43
|
+
const candidates = arityMatches.length > 0 ? arityMatches : overloads;
|
|
44
|
+
if (argTypes !== undefined && argTypes.length > 0) {
|
|
45
|
+
const typed = candidates.filter((d) => {
|
|
46
|
+
const params = d.parameterTypes;
|
|
47
|
+
if (params === undefined)
|
|
48
|
+
return false;
|
|
49
|
+
for (let i = 0; i < argTypes.length && i < params.length; i++) {
|
|
50
|
+
if (argTypes[i] === '')
|
|
51
|
+
continue;
|
|
52
|
+
if (argTypes[i] !== params[i])
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
});
|
|
57
|
+
if (typed.length > 0)
|
|
58
|
+
return typed;
|
|
59
|
+
}
|
|
60
|
+
return candidates;
|
|
61
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Receiver-bound CALLS / ACCESSES emit pass — generic 7-case
|
|
3
|
+
* dispatcher consuming `ScopeResolver` for the language-specific bits
|
|
4
|
+
* (super recognizer, field-fallback toggle).
|
|
5
|
+
*
|
|
6
|
+
* **Contract Invariant I4 — case order is load-bearing.** The cases
|
|
7
|
+
* are evaluated in this order; the FIRST that emits an edge wins:
|
|
8
|
+
*
|
|
9
|
+
* 1. **super branch** — `provider.isSuperReceiver(receiverName)` →
|
|
10
|
+
* MRO walk skipping self
|
|
11
|
+
* 2. **Case 0 (compound)** — receiver has `.` or `(` → compound resolver
|
|
12
|
+
* 3. **Case 1 (namespace)** — receiver in `namespaceTargets` → exported def
|
|
13
|
+
* 4. **Case 2 (class-name / static receiver)** — receiver resolves to a
|
|
14
|
+
* class-like binding (Class/Interface/Struct/Record/Enum/Trait) → MRO
|
|
15
|
+
* walk on that class. Also handles static-style invocations
|
|
16
|
+
* (`ILogger.Warn(...)`) with kind-aware reason/confidence for
|
|
17
|
+
* read/write ACCESSES.
|
|
18
|
+
* 5. **Case 3 (dotted typeBinding for namespace prefix)** —
|
|
19
|
+
* `typeRef.rawName` like `models.User`
|
|
20
|
+
* 6. **Case 3b (chain-typebinding)** — `typeRef.rawName` has a dot
|
|
21
|
+
* but not a namespace prefix → compound resolver
|
|
22
|
+
* 7. **Case 4 (simple typeBinding)** — `typeRef.rawName` has no dot →
|
|
23
|
+
* MRO walk + `findOwnedMember`
|
|
24
|
+
*
|
|
25
|
+
* Reordering or merging cases changes resolution semantics.
|
|
26
|
+
*
|
|
27
|
+
* **Contract Invariant I5 — pre-seeding `seen` is forbidden.** The
|
|
28
|
+
* orchestrator runs this pass FIRST (before `emitReferencesViaLookup`)
|
|
29
|
+
* and consumes the populated `handledSites` set. Pre-seeding `seen`
|
|
30
|
+
* from the shared resolver's emissions (an old optimization) actively
|
|
31
|
+
* suppresses correct emissions for sites the shared resolver also
|
|
32
|
+
* resolved to a wrong target.
|
|
33
|
+
*/
|
|
34
|
+
import type { ParsedFile } from '../../../../_shared/index.js';
|
|
35
|
+
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
36
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
37
|
+
import type { SemanticModel } from '../../model/semantic-model.js';
|
|
38
|
+
import type { ScopeResolver } from '../contract/scope-resolver.js';
|
|
39
|
+
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
40
|
+
import type { WorkspaceResolutionIndex } from '../workspace-index.js';
|
|
41
|
+
/** Subset of `ScopeResolver` consumed by this pass. Accepting the
|
|
42
|
+
* subset rather than the full provider keeps tests and partial
|
|
43
|
+
* refactors lighter — callers only need to populate what we read. */
|
|
44
|
+
type ReceiverBoundProviderSubset = Pick<ScopeResolver, 'isSuperReceiver' | 'fieldFallbackOnMethodLookup' | 'collapseMemberCallsByCallerTarget' | 'unwrapCollectionAccessor' | 'hoistTypeBindingsToModule'>;
|
|
45
|
+
export declare function emitReceiverBoundCalls(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, handledSites: Set<string>, provider: ReceiverBoundProviderSubset, index: WorkspaceResolutionIndex, model: SemanticModel): number;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Receiver-bound CALLS / ACCESSES emit pass — generic 7-case
|
|
3
|
+
* dispatcher consuming `ScopeResolver` for the language-specific bits
|
|
4
|
+
* (super recognizer, field-fallback toggle).
|
|
5
|
+
*
|
|
6
|
+
* **Contract Invariant I4 — case order is load-bearing.** The cases
|
|
7
|
+
* are evaluated in this order; the FIRST that emits an edge wins:
|
|
8
|
+
*
|
|
9
|
+
* 1. **super branch** — `provider.isSuperReceiver(receiverName)` →
|
|
10
|
+
* MRO walk skipping self
|
|
11
|
+
* 2. **Case 0 (compound)** — receiver has `.` or `(` → compound resolver
|
|
12
|
+
* 3. **Case 1 (namespace)** — receiver in `namespaceTargets` → exported def
|
|
13
|
+
* 4. **Case 2 (class-name / static receiver)** — receiver resolves to a
|
|
14
|
+
* class-like binding (Class/Interface/Struct/Record/Enum/Trait) → MRO
|
|
15
|
+
* walk on that class. Also handles static-style invocations
|
|
16
|
+
* (`ILogger.Warn(...)`) with kind-aware reason/confidence for
|
|
17
|
+
* read/write ACCESSES.
|
|
18
|
+
* 5. **Case 3 (dotted typeBinding for namespace prefix)** —
|
|
19
|
+
* `typeRef.rawName` like `models.User`
|
|
20
|
+
* 6. **Case 3b (chain-typebinding)** — `typeRef.rawName` has a dot
|
|
21
|
+
* but not a namespace prefix → compound resolver
|
|
22
|
+
* 7. **Case 4 (simple typeBinding)** — `typeRef.rawName` has no dot →
|
|
23
|
+
* MRO walk + `findOwnedMember`
|
|
24
|
+
*
|
|
25
|
+
* Reordering or merging cases changes resolution semantics.
|
|
26
|
+
*
|
|
27
|
+
* **Contract Invariant I5 — pre-seeding `seen` is forbidden.** The
|
|
28
|
+
* orchestrator runs this pass FIRST (before `emitReferencesViaLookup`)
|
|
29
|
+
* and consumes the populated `handledSites` set. Pre-seeding `seen`
|
|
30
|
+
* from the shared resolver's emissions (an old optimization) actively
|
|
31
|
+
* suppresses correct emissions for sites the shared resolver also
|
|
32
|
+
* resolved to a wrong target.
|
|
33
|
+
*/
|
|
34
|
+
import { collectNamespaceTargets } from '../scope/namespace-targets.js';
|
|
35
|
+
import { findClassBindingInScope, findEnclosingClassDef, findExportedDef, findOwnedMember, findReceiverTypeBinding, } from '../scope/walkers.js';
|
|
36
|
+
import { tryEmitEdge } from '../graph-bridge/edges.js';
|
|
37
|
+
import { resolveCompoundReceiverClass } from '../passes/compound-receiver.js';
|
|
38
|
+
import { resolveDefGraphId } from '../graph-bridge/ids.js';
|
|
39
|
+
import { narrowOverloadCandidates } from './overload-narrowing.js';
|
|
40
|
+
export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, handledSites, provider, index, model) {
|
|
41
|
+
let emitted = 0;
|
|
42
|
+
// Per-pass dedup so the multiple cases don't double-emit if two of
|
|
43
|
+
// them resolve the same site to the same target. NEVER pre-seed
|
|
44
|
+
// from the reference index — see Contract Invariant I5.
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
const fieldFallback = provider.fieldFallbackOnMethodLookup ?? true;
|
|
47
|
+
const collapse = provider.collapseMemberCallsByCallerTarget === true;
|
|
48
|
+
const hoistTypeBindingsToModule = provider.hoistTypeBindingsToModule === true;
|
|
49
|
+
const compoundOpts = {
|
|
50
|
+
fieldFallback,
|
|
51
|
+
unwrapCollectionAccessor: provider.unwrapCollectionAccessor,
|
|
52
|
+
hoistTypeBindingsToModule,
|
|
53
|
+
};
|
|
54
|
+
// Build an interface → implementors map from IMPLEMENTS edges.
|
|
55
|
+
// Maps Interface graph-id → list of implementor class scope-def-ids.
|
|
56
|
+
// We translate graph-ids back to scope-resolution DefIds via
|
|
57
|
+
// `parsedFiles.localDefs` lookup so downstream `findOwnedMember`
|
|
58
|
+
// (which keys by DefId) can find the implementor's members.
|
|
59
|
+
const graphIdToClassDef = new Map();
|
|
60
|
+
for (const parsed of parsedFiles) {
|
|
61
|
+
for (const def of parsed.localDefs) {
|
|
62
|
+
if (def.type !== 'Class' && def.type !== 'Interface')
|
|
63
|
+
continue;
|
|
64
|
+
const graphId = resolveDefGraphId(parsed.filePath, def, nodeLookup);
|
|
65
|
+
if (graphId !== undefined)
|
|
66
|
+
graphIdToClassDef.set(graphId, def);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const implementorsByInterfaceDefId = new Map();
|
|
70
|
+
for (const rel of graph.iterRelationshipsByType('IMPLEMENTS')) {
|
|
71
|
+
const ifaceDef = graphIdToClassDef.get(rel.targetId);
|
|
72
|
+
const implDef = graphIdToClassDef.get(rel.sourceId);
|
|
73
|
+
if (ifaceDef === undefined || implDef === undefined)
|
|
74
|
+
continue;
|
|
75
|
+
let list = implementorsByInterfaceDefId.get(ifaceDef.nodeId);
|
|
76
|
+
if (list === undefined) {
|
|
77
|
+
list = [];
|
|
78
|
+
implementorsByInterfaceDefId.set(ifaceDef.nodeId, list);
|
|
79
|
+
}
|
|
80
|
+
list.push(implDef);
|
|
81
|
+
}
|
|
82
|
+
/** Emit secondary CALLS edges with reason='interface-dispatch'
|
|
83
|
+
* when the primary receiver-typed edge targeted an Interface's
|
|
84
|
+
* method. Each implementing class's same-named method gets a
|
|
85
|
+
* secondary edge (excluding the primary target itself). */
|
|
86
|
+
const emitInterfaceDispatchFor = (ownerDef, memberName, primaryMemberDef, site, confidence) => {
|
|
87
|
+
if (ownerDef.type !== 'Interface')
|
|
88
|
+
return 0;
|
|
89
|
+
const impls = implementorsByInterfaceDefId.get(ownerDef.nodeId);
|
|
90
|
+
if (impls === undefined)
|
|
91
|
+
return 0;
|
|
92
|
+
let n = 0;
|
|
93
|
+
for (const implDef of impls) {
|
|
94
|
+
const implMember = findOwnedMember(implDef.nodeId, memberName, model);
|
|
95
|
+
if (implMember === undefined)
|
|
96
|
+
continue;
|
|
97
|
+
if (implMember.nodeId === primaryMemberDef.nodeId)
|
|
98
|
+
continue;
|
|
99
|
+
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, implMember, 'interface-dispatch', seen, confidence, collapse);
|
|
100
|
+
if (ok)
|
|
101
|
+
n++;
|
|
102
|
+
}
|
|
103
|
+
return n;
|
|
104
|
+
};
|
|
105
|
+
for (const parsed of parsedFiles) {
|
|
106
|
+
const namespaceTargets = collectNamespaceTargets(parsed, scopes);
|
|
107
|
+
for (const site of parsed.referenceSites) {
|
|
108
|
+
if (site.kind !== 'call' && site.kind !== 'read' && site.kind !== 'write')
|
|
109
|
+
continue;
|
|
110
|
+
if (site.explicitReceiver === undefined)
|
|
111
|
+
continue;
|
|
112
|
+
const receiverName = site.explicitReceiver.name;
|
|
113
|
+
const memberName = site.name;
|
|
114
|
+
const siteKey = `${parsed.filePath}:${site.atRange.startLine}:${site.atRange.startCol}`;
|
|
115
|
+
// ── super branch ─────────────────────────────────────────────
|
|
116
|
+
if (provider.isSuperReceiver(receiverName)) {
|
|
117
|
+
const enclosingClass = findEnclosingClassDef(site.inScope, scopes);
|
|
118
|
+
if (enclosingClass !== undefined) {
|
|
119
|
+
const ancestors = scopes.methodDispatch.mroFor(enclosingClass.nodeId);
|
|
120
|
+
let memberDef;
|
|
121
|
+
for (const ownerId of ancestors) {
|
|
122
|
+
memberDef = findOwnedMember(ownerId, memberName, model);
|
|
123
|
+
if (memberDef !== undefined)
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
if (memberDef !== undefined) {
|
|
127
|
+
// Super/base calls resolve through the MRO chain, not
|
|
128
|
+
// through imports — the ancestor method is found by
|
|
129
|
+
// walking `methodDispatch.mroFor(enclosingClass)`, which
|
|
130
|
+
// is independent of whether a `using` / `import` directive
|
|
131
|
+
// brought the ancestor into scope. We emit the canonical
|
|
132
|
+
// `'global'` tier (ARCHITECTURE.md § Scope-Resolution
|
|
133
|
+
// Pipeline — edge vocabulary).
|
|
134
|
+
//
|
|
135
|
+
// Known legacy-path asymmetry: the C# legacy DAG also
|
|
136
|
+
// classifies `base.Save()` as `'global'` (same-graph); the
|
|
137
|
+
// Python legacy DAG classifies `super().save()` as
|
|
138
|
+
// `'import-resolved'` because Python's ancestor lookup
|
|
139
|
+
// flows through `typeEnv.lookup(...)` which resolves the
|
|
140
|
+
// superclass via its `import`/`from … import …` binding.
|
|
141
|
+
// Closing that gap requires realigning the legacy tier
|
|
142
|
+
// classifier and is tracked separately.
|
|
143
|
+
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, 'global', seen, 0.85, collapse);
|
|
144
|
+
if (ok)
|
|
145
|
+
emitted++;
|
|
146
|
+
// Always mark handled when the site was resolved, even
|
|
147
|
+
// if the edge was deduplicated (collapse mode), so
|
|
148
|
+
// `emitReferencesViaLookup` doesn't re-emit from the
|
|
149
|
+
// reference index.
|
|
150
|
+
handledSites.add(siteKey);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// ── Case 0: compound receiver ────────────────────────────────
|
|
156
|
+
if (receiverName.includes('.') || receiverName.includes('(')) {
|
|
157
|
+
const currentClass = resolveCompoundReceiverClass(receiverName, site.inScope, scopes, index, compoundOpts);
|
|
158
|
+
if (currentClass !== undefined) {
|
|
159
|
+
const chain = [currentClass.nodeId, ...scopes.methodDispatch.mroFor(currentClass.nodeId)];
|
|
160
|
+
let memberDef;
|
|
161
|
+
for (const ownerId of chain) {
|
|
162
|
+
memberDef = findOwnedMember(ownerId, memberName, model);
|
|
163
|
+
if (memberDef !== undefined)
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
if (memberDef !== undefined) {
|
|
167
|
+
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
|
|
168
|
+
if (ok)
|
|
169
|
+
emitted++;
|
|
170
|
+
// Always mark handled when the site was resolved, even
|
|
171
|
+
// if the edge was deduplicated (collapse mode), so
|
|
172
|
+
// `emitReferencesViaLookup` doesn't re-emit from the
|
|
173
|
+
// reference index.
|
|
174
|
+
handledSites.add(siteKey);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// ── Case 1: namespace receiver ───────────────────────────────
|
|
180
|
+
const targetFile = namespaceTargets.get(receiverName);
|
|
181
|
+
if (targetFile !== undefined) {
|
|
182
|
+
const memberDef = findExportedDef(targetFile, memberName, index);
|
|
183
|
+
if (memberDef !== undefined) {
|
|
184
|
+
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
|
|
185
|
+
if (ok)
|
|
186
|
+
emitted++;
|
|
187
|
+
handledSites.add(siteKey);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// ── Case 2: class-name receiver ──────────────────────────────
|
|
192
|
+
const classDef = findClassBindingInScope(site.inScope, receiverName, scopes);
|
|
193
|
+
if (classDef !== undefined) {
|
|
194
|
+
const chain = [classDef.nodeId, ...scopes.methodDispatch.mroFor(classDef.nodeId)];
|
|
195
|
+
let memberDef;
|
|
196
|
+
for (const ownerId of chain) {
|
|
197
|
+
memberDef = findOwnedMember(ownerId, memberName, model);
|
|
198
|
+
if (memberDef !== undefined)
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
if (memberDef !== undefined) {
|
|
202
|
+
const reason = site.kind === 'write' || site.kind === 'read'
|
|
203
|
+
? site.kind
|
|
204
|
+
: memberDef.filePath !== parsed.filePath
|
|
205
|
+
? 'import-resolved'
|
|
206
|
+
: 'global';
|
|
207
|
+
const confidence = site.kind === 'write' || site.kind === 'read' ? 1.0 : 0.85;
|
|
208
|
+
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, reason, seen, confidence, collapse);
|
|
209
|
+
if (ok)
|
|
210
|
+
emitted++;
|
|
211
|
+
handledSites.add(siteKey);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// ── Case 3: dotted typeBinding (`u: models.User`) ────────────
|
|
216
|
+
const typeRef = findReceiverTypeBinding(site.inScope, receiverName, scopes);
|
|
217
|
+
if (typeRef !== undefined && typeRef.rawName.includes('.')) {
|
|
218
|
+
const [nsName, ...classNameParts] = typeRef.rawName.split('.');
|
|
219
|
+
const className = classNameParts.join('.');
|
|
220
|
+
const targetFile3 = namespaceTargets.get(nsName);
|
|
221
|
+
if (targetFile3 !== undefined && className.length > 0) {
|
|
222
|
+
const classDef3 = findExportedDef(targetFile3, className, index);
|
|
223
|
+
if (classDef3 !== undefined) {
|
|
224
|
+
const memberDef = findOwnedMember(classDef3.nodeId, memberName, model);
|
|
225
|
+
if (memberDef !== undefined) {
|
|
226
|
+
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen);
|
|
227
|
+
if (ok) {
|
|
228
|
+
emitted++;
|
|
229
|
+
handledSites.add(siteKey);
|
|
230
|
+
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// ── Case 3b: chain-typebinding (`city → user.get_city`) ──────
|
|
237
|
+
if (typeRef !== undefined &&
|
|
238
|
+
typeRef.rawName.includes('.') &&
|
|
239
|
+
!typeRef.rawName.includes('(') &&
|
|
240
|
+
!namespaceTargets.has(typeRef.rawName.split('.')[0])) {
|
|
241
|
+
// Try the plain dotted-field walk first — covers property /
|
|
242
|
+
// collection-accessor shapes (`.Values`, Kotlin `.size`) and
|
|
243
|
+
// field chains. Fall back to call-form (`x()`) which treats
|
|
244
|
+
// the last segment as a method invocation.
|
|
245
|
+
let ownerDef = resolveCompoundReceiverClass(typeRef.rawName, typeRef.declaredAtScope, scopes, index, compoundOpts);
|
|
246
|
+
if (ownerDef === undefined) {
|
|
247
|
+
ownerDef = resolveCompoundReceiverClass(typeRef.rawName + '()', typeRef.declaredAtScope, scopes, index, compoundOpts);
|
|
248
|
+
}
|
|
249
|
+
if (ownerDef !== undefined) {
|
|
250
|
+
const chain = [ownerDef.nodeId, ...scopes.methodDispatch.mroFor(ownerDef.nodeId)];
|
|
251
|
+
let memberDef;
|
|
252
|
+
for (const ownerId of chain) {
|
|
253
|
+
memberDef = findOwnedMember(ownerId, memberName, model);
|
|
254
|
+
if (memberDef !== undefined)
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
if (memberDef !== undefined) {
|
|
258
|
+
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
|
|
259
|
+
if (ok)
|
|
260
|
+
emitted++;
|
|
261
|
+
// Always mark handled when the site was resolved, even
|
|
262
|
+
// if the edge was deduplicated (collapse mode), so
|
|
263
|
+
// `emitReferencesViaLookup` doesn't re-emit from the
|
|
264
|
+
// reference index.
|
|
265
|
+
handledSites.add(siteKey);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// ── Case 4: simple typeBinding (`u: U`) ──────────────────────
|
|
271
|
+
if (typeRef !== undefined && !typeRef.rawName.includes('.')) {
|
|
272
|
+
const ownerDef = findClassBindingInScope(site.inScope, typeRef.rawName, scopes);
|
|
273
|
+
if (ownerDef !== undefined) {
|
|
274
|
+
const chain = [ownerDef.nodeId, ...scopes.methodDispatch.mroFor(ownerDef.nodeId)];
|
|
275
|
+
let memberDef;
|
|
276
|
+
for (const ownerId of chain) {
|
|
277
|
+
memberDef = pickOverload(ownerId, memberName, site, model);
|
|
278
|
+
if (memberDef !== undefined)
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
if (memberDef !== undefined) {
|
|
282
|
+
// For read/write ACCESSES, mirror the legacy DAG's reason
|
|
283
|
+
// convention so consumers asserting `reason === 'write'`
|
|
284
|
+
// keep working.
|
|
285
|
+
const reason = site.kind === 'write' || site.kind === 'read'
|
|
286
|
+
? site.kind
|
|
287
|
+
: memberDef.filePath !== parsed.filePath
|
|
288
|
+
? 'import-resolved'
|
|
289
|
+
: 'global';
|
|
290
|
+
const confidence = site.kind === 'write' || site.kind === 'read' ? 1.0 : 0.85;
|
|
291
|
+
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, reason, seen, confidence, collapse);
|
|
292
|
+
if (ok)
|
|
293
|
+
emitted++;
|
|
294
|
+
// Interface dispatch: when the primary owner is an
|
|
295
|
+
// Interface, emit secondary CALLS edges to every
|
|
296
|
+
// implementing class's same-named method.
|
|
297
|
+
emitted += emitInterfaceDispatchFor(ownerDef, memberName, memberDef, site, confidence);
|
|
298
|
+
// Always mark handled when the site was resolved, even
|
|
299
|
+
// if the edge was deduplicated (collapse mode), so
|
|
300
|
+
// `emitReferencesViaLookup` doesn't re-emit from the
|
|
301
|
+
// reference index.
|
|
302
|
+
handledSites.add(siteKey);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return emitted;
|
|
310
|
+
}
|
|
311
|
+
/** Resolve a member by name on a class def, narrowing by argument
|
|
312
|
+
* types when multiple overloads share the name. Falls back to the
|
|
313
|
+
* first-seen def (legacy `findOwnedMember` semantics) when there's
|
|
314
|
+
* no narrowing signal or when `argumentTypes` is unavailable. */
|
|
315
|
+
function pickOverload(ownerId, memberName, site, model) {
|
|
316
|
+
const overloads = model.methods.lookupAllByOwner(ownerId, memberName);
|
|
317
|
+
if (overloads.length === 0) {
|
|
318
|
+
// Non-callable member (field / property / variable) — ACCESSES
|
|
319
|
+
// write/read sites target these too. Fall back to the field
|
|
320
|
+
// registry so owner-scoped attribute access resolves.
|
|
321
|
+
return model.fields.lookupFieldByOwner(ownerId, memberName);
|
|
322
|
+
}
|
|
323
|
+
if (overloads.length === 1)
|
|
324
|
+
return overloads[0];
|
|
325
|
+
const candidates = narrowOverloadCandidates(overloads, site.arity, site.argumentTypes);
|
|
326
|
+
return candidates[0] ?? overloads[0];
|
|
327
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: scopeResolution
|
|
3
|
+
*
|
|
4
|
+
* Generic registry-primary resolution phase (RFC #909 Ring 3).
|
|
5
|
+
*
|
|
6
|
+
* For every language in `MIGRATED_LANGUAGES` (per-language flag set)
|
|
7
|
+
* whose provider is registered in `SCOPE_RESOLVERS`:
|
|
8
|
+
* 1. Filter scanned files by language extension.
|
|
9
|
+
* 2. Read file contents.
|
|
10
|
+
* 3. Drive the scope-based pipeline end-to-end via the generic
|
|
11
|
+
* `runScopeResolution(input, provider)` orchestrator.
|
|
12
|
+
* 4. Emit IMPORTS / CALLS / ACCESSES / INHERITS / USES edges.
|
|
13
|
+
*
|
|
14
|
+
* Pairs with the per-language gates in `import-processor.ts` and
|
|
15
|
+
* `call-processor.ts` that skip files when their language is registry-
|
|
16
|
+
* primary, so we don't double-emit edges from both code paths.
|
|
17
|
+
*
|
|
18
|
+
* Adding a language is two changes:
|
|
19
|
+
* - Implement `ScopeResolver` in `languages/<lang>/scope-resolver.ts`
|
|
20
|
+
* and register it in `scope-resolution/pipeline/registry.ts`.
|
|
21
|
+
* - Add the language to `MIGRATED_LANGUAGES` in
|
|
22
|
+
* `registry-primary-flag.ts`.
|
|
23
|
+
*
|
|
24
|
+
* @deps parse (needs Symbol nodes already in the graph so emit-references
|
|
25
|
+
* can attach edges to existing Function/Method/Class nodes)
|
|
26
|
+
* @reads scannedFiles
|
|
27
|
+
* @writes graph (IMPORTS, CALLS, ACCESSES, INHERITS, USES)
|
|
28
|
+
*/
|
|
29
|
+
import type { PipelinePhase } from '../../pipeline-phases/types.js';
|
|
30
|
+
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
31
|
+
export interface ScopeResolutionOutput {
|
|
32
|
+
/** True when at least one language ran. */
|
|
33
|
+
readonly ran: boolean;
|
|
34
|
+
/** Files seen across all languages. `0` when `ran === false`. */
|
|
35
|
+
readonly filesProcessed: number;
|
|
36
|
+
/** IMPORTS edges emitted across all languages. */
|
|
37
|
+
readonly importsEmitted: number;
|
|
38
|
+
/** Reference (CALLS / ACCESSES / INHERITS / USES) edges emitted. */
|
|
39
|
+
readonly referenceEdgesEmitted: number;
|
|
40
|
+
/** Per-language breakdown for telemetry / shadow-parity. */
|
|
41
|
+
readonly perLanguage: ReadonlyMap<SupportedLanguages, {
|
|
42
|
+
readonly filesProcessed: number;
|
|
43
|
+
readonly importsEmitted: number;
|
|
44
|
+
readonly referenceEdgesEmitted: number;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
export declare const scopeResolutionPhase: PipelinePhase<ScopeResolutionOutput>;
|