gitnexus 1.6.0 → 1.6.2-rc.1
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 +73 -0
- package/dist/cli/analyze.js +50 -3
- package/dist/core/group/extractors/fs-utils.d.ts +10 -0
- package/dist/core/group/extractors/fs-utils.js +24 -0
- package/dist/core/group/extractors/grpc-extractor.d.ts +17 -8
- package/dist/core/group/extractors/grpc-extractor.js +328 -191
- package/dist/core/group/extractors/grpc-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/go.js +97 -0
- package/dist/core/group/extractors/grpc-patterns/index.d.ts +19 -0
- package/dist/core/group/extractors/grpc-patterns/index.js +46 -0
- package/dist/core/group/extractors/grpc-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/java.js +173 -0
- package/dist/core/group/extractors/grpc-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/grpc-patterns/node.js +290 -0
- package/dist/core/group/extractors/grpc-patterns/proto.d.ts +9 -0
- package/dist/core/group/extractors/grpc-patterns/proto.js +134 -0
- package/dist/core/group/extractors/grpc-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/python.js +67 -0
- package/dist/core/group/extractors/grpc-patterns/types.d.ts +50 -0
- package/dist/core/group/extractors/grpc-patterns/types.js +1 -0
- package/dist/core/group/extractors/http-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/go.js +215 -0
- package/dist/core/group/extractors/http-patterns/index.d.ts +17 -0
- package/dist/core/group/extractors/http-patterns/index.js +44 -0
- package/dist/core/group/extractors/http-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/java.js +253 -0
- package/dist/core/group/extractors/http-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/http-patterns/node.js +354 -0
- package/dist/core/group/extractors/http-patterns/php.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/php.js +70 -0
- package/dist/core/group/extractors/http-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/python.js +133 -0
- package/dist/core/group/extractors/http-patterns/types.d.ts +61 -0
- package/dist/core/group/extractors/http-patterns/types.js +1 -0
- package/dist/core/group/extractors/http-route-extractor.d.ts +10 -13
- package/dist/core/group/extractors/http-route-extractor.js +231 -238
- package/dist/core/group/extractors/manifest-extractor.d.ts +54 -0
- package/dist/core/group/extractors/manifest-extractor.js +277 -0
- package/dist/core/group/extractors/topic-extractor.d.ts +0 -1
- package/dist/core/group/extractors/topic-extractor.js +55 -192
- package/dist/core/group/extractors/topic-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/go.js +120 -0
- package/dist/core/group/extractors/topic-patterns/index.d.ts +14 -0
- package/dist/core/group/extractors/topic-patterns/index.js +38 -0
- package/dist/core/group/extractors/topic-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/java.js +80 -0
- package/dist/core/group/extractors/topic-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/topic-patterns/node.js +155 -0
- package/dist/core/group/extractors/topic-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/python.js +116 -0
- package/dist/core/group/extractors/topic-patterns/types.d.ts +25 -0
- package/dist/core/group/extractors/topic-patterns/types.js +10 -0
- package/dist/core/group/extractors/tree-sitter-scanner.d.ts +113 -0
- package/dist/core/group/extractors/tree-sitter-scanner.js +94 -0
- package/dist/core/ingestion/binding-accumulator.d.ts +22 -17
- package/dist/core/ingestion/binding-accumulator.js +29 -25
- package/dist/core/ingestion/cobol-processor.d.ts +1 -1
- package/dist/core/ingestion/import-processor.js +1 -1
- package/dist/core/ingestion/language-config.js +1 -1
- package/dist/core/ingestion/language-provider.d.ts +32 -5
- package/dist/core/ingestion/languages/c-cpp.js +2 -2
- package/dist/core/ingestion/languages/dart.d.ts +1 -1
- package/dist/core/ingestion/languages/dart.js +2 -2
- package/dist/core/ingestion/languages/go.d.ts +1 -1
- package/dist/core/ingestion/languages/go.js +2 -2
- package/dist/core/ingestion/languages/ruby.js +16 -1
- package/dist/core/ingestion/languages/swift.d.ts +1 -1
- package/dist/core/ingestion/languages/swift.js +2 -2
- package/dist/core/ingestion/markdown-processor.d.ts +1 -1
- package/dist/core/ingestion/method-extractors/configs/jvm.js +1 -0
- package/dist/core/ingestion/method-extractors/configs/ruby.js +1 -0
- package/dist/core/ingestion/method-extractors/generic.d.ts +6 -0
- package/dist/core/ingestion/method-extractors/generic.js +48 -4
- package/dist/core/ingestion/method-types.d.ts +4 -0
- package/dist/core/ingestion/model/resolve.js +103 -48
- package/dist/core/ingestion/model/semantic-model.d.ts +1 -1
- package/dist/core/ingestion/model/semantic-model.js +1 -1
- package/dist/core/ingestion/model/symbol-table.d.ts +7 -7
- package/dist/core/ingestion/model/symbol-table.js +7 -7
- package/dist/core/ingestion/mro-processor.d.ts +1 -1
- package/dist/core/ingestion/mro-processor.js +1 -1
- package/dist/core/ingestion/parsing-processor.js +54 -42
- package/dist/core/ingestion/pipeline-phases/cobol.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/cobol.js +45 -0
- package/dist/core/ingestion/pipeline-phases/communities.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/communities.js +62 -0
- package/dist/core/ingestion/pipeline-phases/cross-file-impl.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +156 -0
- package/dist/core/ingestion/pipeline-phases/cross-file.d.ts +37 -0
- package/dist/core/ingestion/pipeline-phases/cross-file.js +63 -0
- package/dist/core/ingestion/pipeline-phases/index.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/index.js +22 -0
- package/dist/core/ingestion/pipeline-phases/markdown.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/markdown.js +33 -0
- package/dist/core/ingestion/pipeline-phases/mro.d.ts +18 -0
- package/dist/core/ingestion/pipeline-phases/mro.js +36 -0
- package/dist/core/ingestion/pipeline-phases/orm-extraction.d.ts +22 -0
- package/dist/core/ingestion/pipeline-phases/orm-extraction.js +92 -0
- package/dist/core/ingestion/pipeline-phases/orm.d.ts +15 -0
- package/dist/core/ingestion/pipeline-phases/orm.js +74 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +47 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +437 -0
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +49 -0
- package/dist/core/ingestion/pipeline-phases/parse.js +33 -0
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/processes.js +143 -0
- package/dist/core/ingestion/pipeline-phases/routes.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/routes.js +243 -0
- package/dist/core/ingestion/pipeline-phases/runner.d.ts +22 -0
- package/dist/core/ingestion/pipeline-phases/runner.js +203 -0
- package/dist/core/ingestion/pipeline-phases/scan.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/scan.js +46 -0
- package/dist/core/ingestion/pipeline-phases/structure.d.ts +27 -0
- package/dist/core/ingestion/pipeline-phases/structure.js +35 -0
- package/dist/core/ingestion/pipeline-phases/tools.d.ts +20 -0
- package/dist/core/ingestion/pipeline-phases/tools.js +79 -0
- package/dist/core/ingestion/pipeline-phases/types.d.ts +79 -0
- package/dist/core/ingestion/pipeline-phases/types.js +37 -0
- package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.d.ts +70 -0
- package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.js +312 -0
- package/dist/core/ingestion/pipeline.d.ts +16 -10
- package/dist/core/ingestion/pipeline.js +66 -1534
- package/dist/core/ingestion/process-processor.js +1 -1
- package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
- package/dist/core/ingestion/tree-sitter-queries.js +69 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +1 -3
- package/dist/core/ingestion/utils/ast-helpers.js +48 -21
- package/dist/core/ingestion/utils/env.d.ts +10 -0
- package/dist/core/ingestion/utils/env.js +10 -0
- package/dist/core/ingestion/utils/graph-sort.d.ts +58 -0
- package/dist/core/ingestion/utils/graph-sort.js +100 -0
- package/dist/core/ingestion/workers/parse-worker.js +12 -8
- package/dist/core/lbug/lbug-adapter.d.ts +28 -0
- package/dist/core/lbug/lbug-adapter.js +162 -57
- package/package.json +3 -3
- package/vendor/tree-sitter-proto/binding.gyp +30 -0
- package/vendor/tree-sitter-proto/bindings/node/binding.cc +20 -0
- package/vendor/tree-sitter-proto/bindings/node/index.d.ts +28 -0
- package/vendor/tree-sitter-proto/bindings/node/index.js +7 -0
- package/vendor/tree-sitter-proto/package.json +18 -0
- package/vendor/tree-sitter-proto/src/node-types.json +1145 -0
- package/vendor/tree-sitter-proto/src/parser.c +10149 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/alloc.h +54 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/array.h +291 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/parser.h +266 -0
|
@@ -1,10 +1,53 @@
|
|
|
1
1
|
// gitnexus/src/core/ingestion/method-extractors/generic.ts
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Node types that imply static member semantics when they appear as the owner
|
|
4
|
+
* of a method (Kotlin companion objects, Kotlin top-level `object` declarations,
|
|
5
|
+
* Ruby `class << self` singleton classes). A config that lists any of these in
|
|
6
|
+
* `typeDeclarationNodes` MUST also include the same node type in
|
|
7
|
+
* `staticOwnerTypes` — otherwise methods inside these containers silently get
|
|
8
|
+
* `isStatic=false`, which is a correctness bug that previously only surfaced
|
|
9
|
+
* at analysis time on large repos.
|
|
10
|
+
*
|
|
11
|
+
* Opt-out: a config that sets `staticOwnerTypes: new Set()` (explicit empty
|
|
12
|
+
* set) signals "I handle static-ness entirely via isStatic()" and is exempt
|
|
13
|
+
* from the guard.
|
|
14
|
+
*/
|
|
15
|
+
const STATIC_IMPLYING_OWNER_TYPES = new Set([
|
|
16
|
+
'companion_object',
|
|
17
|
+
'object_declaration',
|
|
18
|
+
'singleton_class',
|
|
19
|
+
]);
|
|
4
20
|
/**
|
|
5
21
|
* Create a MethodExtractor from a declarative config.
|
|
22
|
+
*
|
|
23
|
+
* @throws {Error} if `typeDeclarationNodes` contains a static-implying owner
|
|
24
|
+
* type (companion_object / object_declaration / singleton_class) that is
|
|
25
|
+
* not covered by `staticOwnerTypes`. The guard fires once per language at
|
|
26
|
+
* provider construction to prevent silent `isStatic=false` regressions. See
|
|
27
|
+
* `STATIC_IMPLYING_OWNER_TYPES` for the exact opt-out convention.
|
|
6
28
|
*/
|
|
7
29
|
export function createMethodExtractor(config) {
|
|
30
|
+
// Runtime invariant: each static-implying container type declared in
|
|
31
|
+
// typeDeclarationNodes must be covered by staticOwnerTypes. An explicit
|
|
32
|
+
// empty Set is treated as intentional opt-out.
|
|
33
|
+
if (config.staticOwnerTypes === undefined) {
|
|
34
|
+
const missing = config.typeDeclarationNodes.filter((t) => STATIC_IMPLYING_OWNER_TYPES.has(t));
|
|
35
|
+
if (missing.length > 0) {
|
|
36
|
+
throw new Error(`[MethodExtractionConfig:${config.language}] typeDeclarationNodes includes static-implying owner type(s) ` +
|
|
37
|
+
`${JSON.stringify(missing)} but staticOwnerTypes is not set. Add ` +
|
|
38
|
+
`'staticOwnerTypes: new Set([${missing.map((t) => `'${t}'`).join(', ')}])' ` +
|
|
39
|
+
`to the config, or set 'staticOwnerTypes: new Set()' to opt out explicitly.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const missing = config.typeDeclarationNodes.filter((t) => STATIC_IMPLYING_OWNER_TYPES.has(t) && !config.staticOwnerTypes.has(t));
|
|
44
|
+
// Explicit empty Set is the opt-out signal; don't second-guess it.
|
|
45
|
+
if (missing.length > 0 && config.staticOwnerTypes.size > 0) {
|
|
46
|
+
throw new Error(`[MethodExtractionConfig:${config.language}] typeDeclarationNodes includes static-implying owner type(s) ` +
|
|
47
|
+
`${JSON.stringify(missing)} that are missing from staticOwnerTypes. ` +
|
|
48
|
+
`Either add them to staticOwnerTypes, or set 'staticOwnerTypes: new Set()' to opt out explicitly.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
8
51
|
const typeDeclarationSet = new Set(config.typeDeclarationNodes);
|
|
9
52
|
const methodNodeSet = new Set(config.methodNodeTypes);
|
|
10
53
|
const bodyNodeSet = new Set(config.bodyNodeTypes);
|
|
@@ -137,8 +180,9 @@ function buildMethod(node, ownerNode, context, config) {
|
|
|
137
180
|
// Domain invariant: abstract methods cannot be final
|
|
138
181
|
if (isAbstract)
|
|
139
182
|
isFinal = false;
|
|
140
|
-
//
|
|
141
|
-
|
|
183
|
+
// Static-owner detection is config-driven: each language declares which
|
|
184
|
+
// container node types imply static (e.g. Ruby singleton_class, Kotlin companion_object).
|
|
185
|
+
const isStatic = (config.staticOwnerTypes?.has(ownerNode.type) ?? false) || config.isStatic(node);
|
|
142
186
|
return {
|
|
143
187
|
name,
|
|
144
188
|
receiverType: config.extractReceiverType?.(node) ?? null,
|
|
@@ -73,6 +73,10 @@ export interface MethodExtractionConfig {
|
|
|
73
73
|
isAsync?: (node: SyntaxNode) => boolean;
|
|
74
74
|
isPartial?: (node: SyntaxNode) => boolean;
|
|
75
75
|
isConst?: (node: SyntaxNode) => boolean;
|
|
76
|
+
/** Owner node types where member functions are effectively static (e.g.
|
|
77
|
+
* Ruby singleton_class, Kotlin companion_object / object_declaration).
|
|
78
|
+
* When the ownerNode matches one of these types, isStatic is forced true. */
|
|
79
|
+
staticOwnerTypes?: ReadonlySet<string>;
|
|
76
80
|
/** Resolve the owner name from a standalone method node (e.g. Go receiver type). */
|
|
77
81
|
extractOwnerName?: (node: SyntaxNode) => string | undefined;
|
|
78
82
|
/** Extract a primary constructor from the owner node itself (e.g. C# 12 class Point(int x, int y)). */
|
|
@@ -47,63 +47,118 @@ function gatherAncestors(classId, parentMap) {
|
|
|
47
47
|
export function c3Linearize(classId, parentMap, cache, inProgress) {
|
|
48
48
|
if (cache.has(classId))
|
|
49
49
|
return cache.get(classId);
|
|
50
|
-
//
|
|
50
|
+
// Iterative C3 linearization using an explicit work stack. The recursive
|
|
51
|
+
// version overflows the call stack on deep class hierarchies (10K+
|
|
52
|
+
// levels in large Android/Java codebases).
|
|
53
|
+
//
|
|
54
|
+
// Strategy: maintain a stack of { classId, phase } frames. Each frame
|
|
55
|
+
// goes through two phases:
|
|
56
|
+
// ENTER (0) – check cache / cycle, push parent frames to compute first
|
|
57
|
+
// MERGE (1) – all parent linearizations are cached, merge them C3-style
|
|
51
58
|
const visiting = inProgress ?? new Set();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
59
|
+
const ENTER = 0;
|
|
60
|
+
const MERGE = 1;
|
|
61
|
+
const stack = [{ id: classId, phase: ENTER }];
|
|
62
|
+
while (stack.length > 0) {
|
|
63
|
+
const frame = stack[stack.length - 1];
|
|
64
|
+
if (frame.phase === ENTER) {
|
|
65
|
+
// ── ENTER phase ─────────────────────────────────────────────
|
|
66
|
+
if (cache.has(frame.id)) {
|
|
67
|
+
stack.pop();
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (visiting.has(frame.id)) {
|
|
71
|
+
// Cycle detected
|
|
72
|
+
cache.set(frame.id, null);
|
|
73
|
+
stack.pop();
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
visiting.add(frame.id);
|
|
77
|
+
const directParents = parentMap.get(frame.id);
|
|
78
|
+
if (!directParents || directParents.length === 0) {
|
|
79
|
+
visiting.delete(frame.id);
|
|
80
|
+
cache.set(frame.id, []);
|
|
81
|
+
stack.pop();
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
// Switch to MERGE phase and push parents that still need computing
|
|
85
|
+
frame.phase = MERGE;
|
|
86
|
+
let allParentsCached = true;
|
|
87
|
+
for (let i = directParents.length - 1; i >= 0; i--) {
|
|
88
|
+
const pid = directParents[i];
|
|
89
|
+
if (!cache.has(pid)) {
|
|
90
|
+
stack.push({ id: pid, phase: ENTER });
|
|
91
|
+
allParentsCached = false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// If all parents are already cached, proceed directly to the MERGE
|
|
95
|
+
// phase below (frame.phase is already MERGE, frame is at stack top).
|
|
96
|
+
// Otherwise, loop back to process the newly-pushed parent frames first.
|
|
97
|
+
if (!allParentsCached) {
|
|
82
98
|
continue;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// ── MERGE phase ───────────────────────────────────────────────
|
|
102
|
+
// directParents is guaranteed non-empty here — the ENTER phase already
|
|
103
|
+
// handles the empty-parents case and pops the frame before switching
|
|
104
|
+
// to MERGE.
|
|
105
|
+
stack.pop();
|
|
106
|
+
const directParents = parentMap.get(frame.id);
|
|
107
|
+
// Build parent linearizations from cache
|
|
108
|
+
const parentLinearizations = [];
|
|
109
|
+
let failed = false;
|
|
110
|
+
for (const pid of directParents) {
|
|
111
|
+
const pLin = cache.get(pid);
|
|
112
|
+
if (pLin === undefined) {
|
|
113
|
+
// Should not happen if phases are ordered correctly, but guard anyway
|
|
114
|
+
failed = true;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
if (pLin === null) {
|
|
118
|
+
// Parent linearization failed (cycle or inconsistent)
|
|
119
|
+
failed = true;
|
|
87
120
|
break;
|
|
88
121
|
}
|
|
122
|
+
parentLinearizations.push([pid, ...pLin]);
|
|
89
123
|
}
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return null;
|
|
124
|
+
if (failed) {
|
|
125
|
+
visiting.delete(frame.id);
|
|
126
|
+
cache.set(frame.id, null);
|
|
127
|
+
continue;
|
|
95
128
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
129
|
+
// Add the direct parents list as the final sequence
|
|
130
|
+
const sequences = [...parentLinearizations, [...directParents]];
|
|
131
|
+
const result = [];
|
|
132
|
+
let inconsistent = false;
|
|
133
|
+
while (sequences.some((s) => s.length > 0)) {
|
|
134
|
+
// Find a good head: one that doesn't appear in the tail of any other sequence
|
|
135
|
+
let head = null;
|
|
136
|
+
for (const seq of sequences) {
|
|
137
|
+
if (seq.length === 0)
|
|
138
|
+
continue;
|
|
139
|
+
const candidate = seq[0];
|
|
140
|
+
const inTail = sequences.some((other) => other.length > 1 && other.indexOf(candidate, 1) !== -1);
|
|
141
|
+
if (!inTail) {
|
|
142
|
+
head = candidate;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (head === null) {
|
|
147
|
+
inconsistent = true;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
result.push(head);
|
|
151
|
+
// Remove the chosen head from all sequences
|
|
152
|
+
for (const seq of sequences) {
|
|
153
|
+
if (seq.length > 0 && seq[0] === head) {
|
|
154
|
+
seq.shift();
|
|
155
|
+
}
|
|
101
156
|
}
|
|
102
157
|
}
|
|
158
|
+
visiting.delete(frame.id);
|
|
159
|
+
cache.set(frame.id, inconsistent ? null : result);
|
|
103
160
|
}
|
|
104
|
-
|
|
105
|
-
cache.set(classId, result);
|
|
106
|
-
return result;
|
|
161
|
+
return cache.get(classId) ?? null;
|
|
107
162
|
}
|
|
108
163
|
// `gatherAncestors` is exported so mro-processor.ts can reuse the same
|
|
109
164
|
// BFS traversal for graph-level MRO emission.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Symbol Table — file-indexed + callable-name symbol storage.
|
|
3
3
|
*
|
|
4
|
-
* This module is a PURE LEAF in the ingestion
|
|
5
|
-
* O(1) indexes:
|
|
4
|
+
* This module is a PURE LEAF in the ingestion dependency hierarchy. It owns
|
|
5
|
+
* two orthogonal O(1) indexes:
|
|
6
6
|
*
|
|
7
7
|
* 1. fileIndex — Map<filePath, Map<name, SymbolDefinition[]>>
|
|
8
8
|
* for same-file lookups (Tier 1 resolution)
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
* for name-keyed callable lookups (Tier 3 widen)
|
|
11
11
|
*
|
|
12
12
|
* SymbolTable deliberately knows NOTHING about the owner-scoped registries
|
|
13
|
-
* (types, methods, fields) that sit above it in the
|
|
14
|
-
* live in `model/` and depend on SymbolTable, not the other way
|
|
15
|
-
* {@link createSemanticModel} composes this pure SymbolTable with the
|
|
13
|
+
* (types, methods, fields) that sit above it in the dependency graph. Those
|
|
14
|
+
* registries live in `model/` and depend on SymbolTable, not the other way
|
|
15
|
+
* around. {@link createSemanticModel} composes this pure SymbolTable with the
|
|
16
16
|
* registries and wraps `add()` to fan out registrations into both layers.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* Dependency direction (strictly enforced):
|
|
19
19
|
*
|
|
20
20
|
* gitnexus-shared (NodeLabel) — leaf type
|
|
21
21
|
* ↑
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
*
|
|
32
32
|
* No arrow ever points downward from this file. If you are tempted to
|
|
33
33
|
* import from `./model/` here, you are going the wrong way — move the
|
|
34
|
-
* logic up the
|
|
34
|
+
* logic up the dependency chain instead.
|
|
35
35
|
*/
|
|
36
36
|
import type { NodeLabel } from '../../../_shared/index.js';
|
|
37
37
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Symbol Table — file-indexed + callable-name symbol storage.
|
|
3
3
|
*
|
|
4
|
-
* This module is a PURE LEAF in the ingestion
|
|
5
|
-
* O(1) indexes:
|
|
4
|
+
* This module is a PURE LEAF in the ingestion dependency hierarchy. It owns
|
|
5
|
+
* two orthogonal O(1) indexes:
|
|
6
6
|
*
|
|
7
7
|
* 1. fileIndex — Map<filePath, Map<name, SymbolDefinition[]>>
|
|
8
8
|
* for same-file lookups (Tier 1 resolution)
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
* for name-keyed callable lookups (Tier 3 widen)
|
|
11
11
|
*
|
|
12
12
|
* SymbolTable deliberately knows NOTHING about the owner-scoped registries
|
|
13
|
-
* (types, methods, fields) that sit above it in the
|
|
14
|
-
* live in `model/` and depend on SymbolTable, not the other way
|
|
15
|
-
* {@link createSemanticModel} composes this pure SymbolTable with the
|
|
13
|
+
* (types, methods, fields) that sit above it in the dependency graph. Those
|
|
14
|
+
* registries live in `model/` and depend on SymbolTable, not the other way
|
|
15
|
+
* around. {@link createSemanticModel} composes this pure SymbolTable with the
|
|
16
16
|
* registries and wraps `add()` to fan out registrations into both layers.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* Dependency direction (strictly enforced):
|
|
19
19
|
*
|
|
20
20
|
* gitnexus-shared (NodeLabel) — leaf type
|
|
21
21
|
* ↑
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
*
|
|
32
32
|
* No arrow ever points downward from this file. If you are tempted to
|
|
33
33
|
* import from `./model/` here, you are going the wrong way — move the
|
|
34
|
-
* logic up the
|
|
34
|
+
* logic up the dependency chain instead.
|
|
35
35
|
*/
|
|
36
36
|
/**
|
|
37
37
|
* Class-like NodeLabels — used for qualifiedName fallback inside
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MRO (Method Resolution Order) Processor
|
|
3
3
|
*
|
|
4
|
-
* Walks the inheritance
|
|
4
|
+
* Walks the inheritance graph (EXTENDS/IMPLEMENTS edges), collects methods from
|
|
5
5
|
* each ancestor via HAS_METHOD edges, detects method-name collisions across
|
|
6
6
|
* parents, and applies language-specific resolution rules to emit METHOD_OVERRIDES edges.
|
|
7
7
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MRO (Method Resolution Order) Processor
|
|
3
3
|
*
|
|
4
|
-
* Walks the inheritance
|
|
4
|
+
* Walks the inheritance graph (EXTENDS/IMPLEMENTS edges), collects methods from
|
|
5
5
|
* each ancestor via HAS_METHOD edges, detects method-name collisions across
|
|
6
6
|
* parents, and applies language-specific resolution rules to emit METHOD_OVERRIDES edges.
|
|
7
7
|
*
|
|
@@ -74,30 +74,30 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
|
|
|
74
74
|
qualifiedName: sym.qualifiedName,
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
-
for (const
|
|
78
|
-
allImports.push(
|
|
79
|
-
for (const
|
|
80
|
-
allCalls.push(
|
|
81
|
-
for (const
|
|
82
|
-
allAssignments.push(
|
|
83
|
-
for (const
|
|
84
|
-
allHeritage.push(
|
|
85
|
-
for (const
|
|
86
|
-
allRoutes.push(
|
|
87
|
-
for (const
|
|
88
|
-
allFetchCalls.push(
|
|
89
|
-
for (const
|
|
90
|
-
allDecoratorRoutes.push(
|
|
91
|
-
for (const
|
|
92
|
-
allToolDefs.push(
|
|
77
|
+
for (const item of result.imports)
|
|
78
|
+
allImports.push(item);
|
|
79
|
+
for (const item of result.calls)
|
|
80
|
+
allCalls.push(item);
|
|
81
|
+
for (const item of result.assignments)
|
|
82
|
+
allAssignments.push(item);
|
|
83
|
+
for (const item of result.heritage)
|
|
84
|
+
allHeritage.push(item);
|
|
85
|
+
for (const item of result.routes)
|
|
86
|
+
allRoutes.push(item);
|
|
87
|
+
for (const item of result.fetchCalls)
|
|
88
|
+
allFetchCalls.push(item);
|
|
89
|
+
for (const item of result.decoratorRoutes)
|
|
90
|
+
allDecoratorRoutes.push(item);
|
|
91
|
+
for (const item of result.toolDefs)
|
|
92
|
+
allToolDefs.push(item);
|
|
93
93
|
if (result.ormQueries)
|
|
94
|
-
for (const
|
|
95
|
-
allORMQueries.push(
|
|
96
|
-
for (const
|
|
97
|
-
allConstructorBindings.push(
|
|
94
|
+
for (const item of result.ormQueries)
|
|
95
|
+
allORMQueries.push(item);
|
|
96
|
+
for (const item of result.constructorBindings)
|
|
97
|
+
allConstructorBindings.push(item);
|
|
98
98
|
if (result.fileScopeBindings)
|
|
99
|
-
for (const
|
|
100
|
-
fileScopeBindingsByFile.push(
|
|
99
|
+
for (const item of result.fileScopeBindings)
|
|
100
|
+
fileScopeBindingsByFile.push(item);
|
|
101
101
|
}
|
|
102
102
|
// Merge and log skipped languages from workers
|
|
103
103
|
const skippedLanguages = new Map();
|
|
@@ -135,11 +135,11 @@ const processParsingWithWorkers = async (graph, files, symbolTable, astCache, wo
|
|
|
135
135
|
// Keyed by tree-sitter node reference — cleared at the start of each file.
|
|
136
136
|
const classInfoCache = new Map();
|
|
137
137
|
const exportCache = new Map();
|
|
138
|
-
const cachedFindEnclosingClassInfo = (node, filePath) => {
|
|
138
|
+
const cachedFindEnclosingClassInfo = (node, filePath, resolveEnclosingOwner) => {
|
|
139
139
|
const cached = classInfoCache.get(node);
|
|
140
140
|
if (cached !== undefined)
|
|
141
141
|
return cached;
|
|
142
|
-
const result = findEnclosingClassInfo(node, filePath);
|
|
142
|
+
const result = findEnclosingClassInfo(node, filePath, resolveEnclosingOwner);
|
|
143
143
|
classInfoCache.set(node, result);
|
|
144
144
|
return result;
|
|
145
145
|
};
|
|
@@ -158,24 +158,34 @@ const seqFieldInfoCache = new Map();
|
|
|
158
158
|
const seqMethodExtractCache = new Map();
|
|
159
159
|
// Derived method map + collision groups cache — avoids rebuilding per method.
|
|
160
160
|
const seqMethodMapCache = new Map();
|
|
161
|
-
|
|
161
|
+
/** Provider-aware enclosing container lookup.
|
|
162
|
+
* Walks up from `node` until a CLASS_CONTAINER_TYPES node is found.
|
|
163
|
+
* When `resolveEnclosingOwner` is provided, delegates language-specific
|
|
164
|
+
* container remapping (e.g., Ruby singleton_class → enclosing class).
|
|
165
|
+
* Without the hook, returns the first matching container directly (raw lookup). */
|
|
166
|
+
function seqFindEnclosingOwnerNode(node, resolveEnclosingOwner) {
|
|
162
167
|
let current = node.parent;
|
|
163
168
|
while (current) {
|
|
164
169
|
if (CLASS_CONTAINER_TYPES.has(current.type)) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
if (resolveEnclosingOwner) {
|
|
171
|
+
const resolved = resolveEnclosingOwner(current);
|
|
172
|
+
if (resolved === null) {
|
|
173
|
+
// Provider says skip this container — keep walking up.
|
|
174
|
+
current = current.parent;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
return resolved;
|
|
178
|
+
}
|
|
168
179
|
return current;
|
|
169
180
|
}
|
|
170
181
|
current = current.parent;
|
|
171
182
|
}
|
|
172
183
|
return null;
|
|
173
184
|
}
|
|
174
|
-
/** Minimal no-op SymbolTable stub for
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
* `as unknown as` cast. */
|
|
185
|
+
/** Minimal no-op SymbolTable stub for sequential extractor contexts. The real
|
|
186
|
+
* SymbolTable is not fully populated yet at this stage, so use the stub for safety.
|
|
187
|
+
* Implements the full {@link SymbolTableReader} surface so future extractor additions
|
|
188
|
+
* don't silently fall off an `as unknown as` cast. */
|
|
179
189
|
const NOOP_SYMBOL_TABLE_SEQ = {
|
|
180
190
|
lookupExact: () => undefined,
|
|
181
191
|
lookupExactFull: () => undefined,
|
|
@@ -318,7 +328,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
|
|
|
318
328
|
nodeLabel === 'Property' ||
|
|
319
329
|
nodeLabel === 'Function';
|
|
320
330
|
const enclosingClassInfo = needsOwner
|
|
321
|
-
? cachedFindEnclosingClassInfo(nameNode || definitionNodeForRange, file.path)
|
|
331
|
+
? cachedFindEnclosingClassInfo(nameNode || definitionNodeForRange, file.path, provider.resolveEnclosingOwner)
|
|
322
332
|
: null;
|
|
323
333
|
const enclosingClassId = enclosingClassInfo?.classId ?? null;
|
|
324
334
|
// Qualify method/property IDs with enclosing class name to avoid collisions
|
|
@@ -339,19 +349,21 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
|
|
|
339
349
|
if (isMethodLike && definitionNode) {
|
|
340
350
|
let enriched = false;
|
|
341
351
|
if (provider.methodExtractor) {
|
|
342
|
-
// Try class-based extraction (method inside a class/struct/trait body)
|
|
343
|
-
|
|
344
|
-
|
|
352
|
+
// Try class-based extraction (method inside a class/struct/trait body).
|
|
353
|
+
// Raw lookup (no resolveEnclosingOwner) so the method extractor sees
|
|
354
|
+
// the actual container node (e.g. singleton_class) for static detection.
|
|
355
|
+
const methodOwnerNode = seqFindEnclosingOwnerNode(definitionNode);
|
|
356
|
+
if (methodOwnerNode) {
|
|
345
357
|
// Cache extract() results per class node to avoid re-traversing the
|
|
346
358
|
// same class body for every method it contains (O(N) -> O(1) per hit).
|
|
347
|
-
let result = seqMethodExtractCache.get(
|
|
359
|
+
let result = seqMethodExtractCache.get(methodOwnerNode.id);
|
|
348
360
|
if (result === undefined) {
|
|
349
361
|
result =
|
|
350
|
-
provider.methodExtractor.extract(
|
|
362
|
+
provider.methodExtractor.extract(methodOwnerNode, {
|
|
351
363
|
filePath: file.path,
|
|
352
364
|
language,
|
|
353
365
|
}) ?? null;
|
|
354
|
-
seqMethodExtractCache.set(
|
|
366
|
+
seqMethodExtractCache.set(methodOwnerNode.id, result);
|
|
355
367
|
}
|
|
356
368
|
if (result?.methods?.length) {
|
|
357
369
|
const defLine = definitionNode.startPosition.row + 1;
|
|
@@ -362,7 +374,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
|
|
|
362
374
|
methodProps = buildMethodProps(info);
|
|
363
375
|
seqDefMethodInfo = info;
|
|
364
376
|
seqDefMethods = result.methods;
|
|
365
|
-
seqClassNodeId =
|
|
377
|
+
seqClassNodeId = methodOwnerNode.id;
|
|
366
378
|
}
|
|
367
379
|
}
|
|
368
380
|
}
|
|
@@ -443,7 +455,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, onF
|
|
|
443
455
|
if (nodeLabel === 'Property' && definitionNode) {
|
|
444
456
|
// FieldExtractor is the single source of truth when available
|
|
445
457
|
if (provider.fieldExtractor && typeEnv) {
|
|
446
|
-
const classNode =
|
|
458
|
+
const classNode = seqFindEnclosingOwnerNode(definitionNode, provider.resolveEnclosingOwner);
|
|
447
459
|
if (classNode) {
|
|
448
460
|
const fieldMap = seqGetFieldInfo(classNode, provider, {
|
|
449
461
|
typeEnv,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: cobol
|
|
3
|
+
*
|
|
4
|
+
* Processes COBOL and JCL files via regex extraction (no tree-sitter).
|
|
5
|
+
*
|
|
6
|
+
* @deps structure
|
|
7
|
+
* @reads scannedFiles, allPaths (from structure phase)
|
|
8
|
+
* @writes graph (COBOL program/paragraph/section nodes, JCL job/step nodes)
|
|
9
|
+
*/
|
|
10
|
+
import type { PipelinePhase } from './types.js';
|
|
11
|
+
export interface CobolOutput {
|
|
12
|
+
programs: number;
|
|
13
|
+
paragraphs: number;
|
|
14
|
+
sections: number;
|
|
15
|
+
}
|
|
16
|
+
export declare const cobolPhase: PipelinePhase<CobolOutput>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: cobol
|
|
3
|
+
*
|
|
4
|
+
* Processes COBOL and JCL files via regex extraction (no tree-sitter).
|
|
5
|
+
*
|
|
6
|
+
* @deps structure
|
|
7
|
+
* @reads scannedFiles, allPaths (from structure phase)
|
|
8
|
+
* @writes graph (COBOL program/paragraph/section nodes, JCL job/step nodes)
|
|
9
|
+
*/
|
|
10
|
+
import { getPhaseOutput } from './types.js';
|
|
11
|
+
import { processCobol, isCobolFile, isJclFile } from '../cobol-processor.js';
|
|
12
|
+
import { readFileContents } from '../filesystem-walker.js';
|
|
13
|
+
import { isDev } from '../utils/env.js';
|
|
14
|
+
export const cobolPhase = {
|
|
15
|
+
name: 'cobol',
|
|
16
|
+
deps: ['structure'],
|
|
17
|
+
async execute(ctx, deps) {
|
|
18
|
+
const { scannedFiles, allPathSet } = getPhaseOutput(deps, 'structure');
|
|
19
|
+
const cobolScanned = scannedFiles.filter((f) => isCobolFile(f.path) || isJclFile(f.path));
|
|
20
|
+
if (cobolScanned.length === 0) {
|
|
21
|
+
return { programs: 0, paragraphs: 0, sections: 0 };
|
|
22
|
+
}
|
|
23
|
+
const cobolContents = await readFileContents(ctx.repoPath, cobolScanned.map((f) => f.path));
|
|
24
|
+
const cobolFiles = cobolScanned
|
|
25
|
+
.filter((f) => cobolContents.has(f.path))
|
|
26
|
+
.map((f) => ({ path: f.path, content: cobolContents.get(f.path) }));
|
|
27
|
+
const cobolResult = processCobol(ctx.graph, cobolFiles, allPathSet);
|
|
28
|
+
if (isDev) {
|
|
29
|
+
console.log(` COBOL: ${cobolResult.programs} programs, ${cobolResult.paragraphs} paragraphs, ${cobolResult.sections} sections from ${cobolFiles.length} files`);
|
|
30
|
+
if (cobolResult.execSqlBlocks > 0 ||
|
|
31
|
+
cobolResult.execCicsBlocks > 0 ||
|
|
32
|
+
cobolResult.entryPoints > 0) {
|
|
33
|
+
console.log(` COBOL enriched: ${cobolResult.execSqlBlocks} SQL blocks, ${cobolResult.execCicsBlocks} CICS blocks, ${cobolResult.entryPoints} entry points, ${cobolResult.moves} moves, ${cobolResult.fileDeclarations} file declarations`);
|
|
34
|
+
}
|
|
35
|
+
if (cobolResult.jclJobs > 0) {
|
|
36
|
+
console.log(` JCL: ${cobolResult.jclJobs} jobs, ${cobolResult.jclSteps} steps`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
programs: cobolResult.programs,
|
|
41
|
+
paragraphs: cobolResult.paragraphs,
|
|
42
|
+
sections: cobolResult.sections,
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: communities
|
|
3
|
+
*
|
|
4
|
+
* Detects code communities via Leiden algorithm and creates
|
|
5
|
+
* Community nodes + MEMBER_OF edges.
|
|
6
|
+
*
|
|
7
|
+
* @deps mro
|
|
8
|
+
* @reads graph (all nodes and relationships)
|
|
9
|
+
* @writes graph (Community nodes, MEMBER_OF edges)
|
|
10
|
+
*/
|
|
11
|
+
import type { PipelinePhase } from './types.js';
|
|
12
|
+
import { type CommunityDetectionResult } from '../community-processor.js';
|
|
13
|
+
export interface CommunitiesOutput {
|
|
14
|
+
communityResult: CommunityDetectionResult;
|
|
15
|
+
}
|
|
16
|
+
export declare const communitiesPhase: PipelinePhase<CommunitiesOutput>;
|