gitnexus 1.6.0 → 1.6.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/dist/cli/analyze.js +28 -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 +313 -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 +201 -238
- package/dist/core/group/extractors/manifest-extractor.d.ts +54 -0
- package/dist/core/group/extractors/manifest-extractor.js +235 -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 +8 -0
- package/dist/core/ingestion/languages/ruby.js +15 -0
- 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 +35 -0
- package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.js +174 -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.js +66 -24
- 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
|
@@ -36,9 +36,12 @@
|
|
|
36
36
|
* or (b) post-process worker-path entries through a follow-up resolution
|
|
37
37
|
* pass after the main-thread `SymbolTable` is complete.
|
|
38
38
|
*
|
|
39
|
-
* **Lifecycle contract**: `append → finalize → consume → dispose`.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* **Lifecycle contract**: single-use — `append* → finalize → consume → dispose`.
|
|
40
|
+
* After `dispose()` the accumulator is permanently dead: any mutating call
|
|
41
|
+
* (`appendFile`) throws, and read methods return empty/undefined as if the
|
|
42
|
+
* accumulator had never been appended to. The instance is not recyclable;
|
|
43
|
+
* construct a new one for a new pipeline run. Finalization and disposal are
|
|
44
|
+
* orthogonal state dimensions and may be invoked in either order.
|
|
42
45
|
*/
|
|
43
46
|
export interface BindingEntry {
|
|
44
47
|
readonly scope: string;
|
|
@@ -119,28 +122,30 @@ export declare class BindingAccumulator {
|
|
|
119
122
|
finalize(): void;
|
|
120
123
|
/**
|
|
121
124
|
* Release the accumulator's heap footprint. Clears both internal storage
|
|
122
|
-
* maps and resets `_totalBindings` to zero. Idempotent
|
|
123
|
-
* `finalize()` — calling `dispose()` does not
|
|
125
|
+
* maps and resets `_totalBindings` to zero. Idempotent — calling twice
|
|
126
|
+
* is a no-op. Orthogonal to `finalize()` — calling `dispose()` does not
|
|
127
|
+
* change the finalized state.
|
|
124
128
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
129
|
+
* **Single-use lifecycle.** This is a one-way terminal transition: the
|
|
130
|
+
* accumulator is not recyclable. Any subsequent `appendFile` call throws
|
|
131
|
+
* (`'BindingAccumulator: use after dispose'`), regardless of whether
|
|
132
|
+
* `finalize()` was called first. Post-dispose reads do not throw —
|
|
133
|
+
* they return empty/undefined state matching a never-appended-to
|
|
134
|
+
* accumulator:
|
|
127
135
|
* - `fileCount === 0`
|
|
128
136
|
* - `totalBindings === 0`
|
|
129
137
|
* - `files()` yields an empty iterator
|
|
130
138
|
* - `getFile(x)` returns `undefined` for all `x`
|
|
131
139
|
* - `fileScopeEntries(x)` returns `[]` for all `x`
|
|
140
|
+
* - `fileScopeGet(x, y)` returns `undefined` for all `x, y`
|
|
132
141
|
* - `estimateMemoryBytes()` returns `0`
|
|
133
142
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* consumers (`processCallsFromExtracted`, `processAssignmentsFromExtracted`)
|
|
141
|
-
* and the ExportedTypeMap enrichment loop have completed, so the heap is
|
|
142
|
-
* released before Phase 14 (`runCrossFileBindingPropagation`) and
|
|
143
|
-
* `runGraphAnalysisPhases` begin their long-running work.
|
|
143
|
+
* Lifecycle note: the pipeline disposes the accumulator inside the
|
|
144
|
+
* `finally` of the `crossFile` phase, which is scheduled after every
|
|
145
|
+
* other accumulator consumer (Phase 9 call/assignment processing and
|
|
146
|
+
* the ExportedTypeMap enrichment loop). The dispose call therefore
|
|
147
|
+
* runs once, on both the happy path and the throw path of the
|
|
148
|
+
* crossFile phase.
|
|
144
149
|
*/
|
|
145
150
|
dispose(): void;
|
|
146
151
|
/** Get all bindings for a file, or undefined if the file is unknown. */
|
|
@@ -36,9 +36,12 @@
|
|
|
36
36
|
* or (b) post-process worker-path entries through a follow-up resolution
|
|
37
37
|
* pass after the main-thread `SymbolTable` is complete.
|
|
38
38
|
*
|
|
39
|
-
* **Lifecycle contract**: `append → finalize → consume → dispose`.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* **Lifecycle contract**: single-use — `append* → finalize → consume → dispose`.
|
|
40
|
+
* After `dispose()` the accumulator is permanently dead: any mutating call
|
|
41
|
+
* (`appendFile`) throws, and read methods return empty/undefined as if the
|
|
42
|
+
* accumulator had never been appended to. The instance is not recyclable;
|
|
43
|
+
* construct a new one for a new pipeline run. Finalization and disposal are
|
|
44
|
+
* orthogonal state dimensions and may be invoked in either order.
|
|
42
45
|
*/
|
|
43
46
|
/**
|
|
44
47
|
* Merge file-scope bindings from a (finalized) `BindingAccumulator` into an
|
|
@@ -137,17 +140,16 @@ export class BindingAccumulator {
|
|
|
137
140
|
if (this._finalized) {
|
|
138
141
|
throw new Error('[BindingAccumulator] appendFile after finalize — no further appends allowed');
|
|
139
142
|
}
|
|
143
|
+
// Single-use lifecycle: once disposed, the accumulator is dead. A
|
|
144
|
+
// post-dispose append almost always indicates a missed wiring step
|
|
145
|
+
// (the consumer is reading state that was supposed to be released),
|
|
146
|
+
// so convert the silent use-after-dispose into a loud failure.
|
|
147
|
+
if (this._disposed) {
|
|
148
|
+
throw new Error('BindingAccumulator: use after dispose');
|
|
149
|
+
}
|
|
140
150
|
if (entries.length === 0) {
|
|
141
151
|
return;
|
|
142
152
|
}
|
|
143
|
-
// Contract consistency: if this accumulator was previously disposed
|
|
144
|
-
// without being finalized, `dispose()` is documented to leave it
|
|
145
|
-
// "behaving like a fresh one" for subsequent appends. Clear the
|
|
146
|
-
// `_disposed` flag here so the `disposed` getter tracks the actual
|
|
147
|
-
// live state, not a stale signal from the prior lifecycle cycle.
|
|
148
|
-
if (this._disposed) {
|
|
149
|
-
this._disposed = false;
|
|
150
|
-
}
|
|
151
153
|
// Note on the file-scope-only invariant:
|
|
152
154
|
// The accumulator does NOT reject function-scope entries at this
|
|
153
155
|
// boundary. The narrowing contract is enforced by the two production
|
|
@@ -213,28 +215,30 @@ export class BindingAccumulator {
|
|
|
213
215
|
}
|
|
214
216
|
/**
|
|
215
217
|
* Release the accumulator's heap footprint. Clears both internal storage
|
|
216
|
-
* maps and resets `_totalBindings` to zero. Idempotent
|
|
217
|
-
* `finalize()` — calling `dispose()` does not
|
|
218
|
+
* maps and resets `_totalBindings` to zero. Idempotent — calling twice
|
|
219
|
+
* is a no-op. Orthogonal to `finalize()` — calling `dispose()` does not
|
|
220
|
+
* change the finalized state.
|
|
218
221
|
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
222
|
+
* **Single-use lifecycle.** This is a one-way terminal transition: the
|
|
223
|
+
* accumulator is not recyclable. Any subsequent `appendFile` call throws
|
|
224
|
+
* (`'BindingAccumulator: use after dispose'`), regardless of whether
|
|
225
|
+
* `finalize()` was called first. Post-dispose reads do not throw —
|
|
226
|
+
* they return empty/undefined state matching a never-appended-to
|
|
227
|
+
* accumulator:
|
|
221
228
|
* - `fileCount === 0`
|
|
222
229
|
* - `totalBindings === 0`
|
|
223
230
|
* - `files()` yields an empty iterator
|
|
224
231
|
* - `getFile(x)` returns `undefined` for all `x`
|
|
225
232
|
* - `fileScopeEntries(x)` returns `[]` for all `x`
|
|
233
|
+
* - `fileScopeGet(x, y)` returns `undefined` for all `x, y`
|
|
226
234
|
* - `estimateMemoryBytes()` returns `0`
|
|
227
235
|
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
* consumers (`processCallsFromExtracted`, `processAssignmentsFromExtracted`)
|
|
235
|
-
* and the ExportedTypeMap enrichment loop have completed, so the heap is
|
|
236
|
-
* released before Phase 14 (`runCrossFileBindingPropagation`) and
|
|
237
|
-
* `runGraphAnalysisPhases` begin their long-running work.
|
|
236
|
+
* Lifecycle note: the pipeline disposes the accumulator inside the
|
|
237
|
+
* `finally` of the `crossFile` phase, which is scheduled after every
|
|
238
|
+
* other accumulator consumer (Phase 9 call/assignment processing and
|
|
239
|
+
* the ExportedTypeMap enrichment loop). The dispose call therefore
|
|
240
|
+
* runs once, on both the happy path and the throw path of the
|
|
241
|
+
* crossFile phase.
|
|
238
242
|
*/
|
|
239
243
|
dispose() {
|
|
240
244
|
this._allByFile.clear();
|
|
@@ -50,5 +50,5 @@ export declare function isJclFile(filePath: string): boolean;
|
|
|
50
50
|
* @param allPathSet - Set of all file paths in the repository
|
|
51
51
|
* @returns Summary of what was extracted
|
|
52
52
|
*/
|
|
53
|
-
export declare const processCobol: (graph: KnowledgeGraph, files: CobolFile[], allPathSet:
|
|
53
|
+
export declare const processCobol: (graph: KnowledgeGraph, files: CobolFile[], allPathSet: ReadonlySet<string>) => CobolProcessResult;
|
|
54
54
|
export {};
|
|
@@ -8,7 +8,7 @@ import { yieldToEventLoop } from './utils/event-loop.js';
|
|
|
8
8
|
import { getTreeSitterBufferSize } from './constants.js';
|
|
9
9
|
import { loadImportConfigs } from './language-config.js';
|
|
10
10
|
import { buildSuffixIndex } from './import-resolvers/utils.js';
|
|
11
|
-
|
|
11
|
+
import { isDev } from './utils/env.js';
|
|
12
12
|
/** Group files by provider (only those with implicit import wiring), then call each wirer
|
|
13
13
|
* with its own language's files. O(n) over files, O(1) per provider lookup. */
|
|
14
14
|
function wireImplicitImports(files, importMap, addImportEdge, projectConfig) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
|
|
3
|
+
import { isDev } from './utils/env.js';
|
|
4
4
|
// ============================================================================
|
|
5
5
|
// LANGUAGE-SPECIFIC CONFIG LOADERS
|
|
6
6
|
// ============================================================================
|
|
@@ -66,6 +66,14 @@ interface LanguageProviderConfig {
|
|
|
66
66
|
* Called with only THIS language's files (pre-grouped by the processor).
|
|
67
67
|
* Default: undefined (no implicit imports). */
|
|
68
68
|
readonly implicitImportWirer?: (languageFiles: string[], importMap: ReadonlyMap<string, ReadonlySet<string>>, addImportEdge: (src: string, target: string) => void, projectConfig: unknown) => void;
|
|
69
|
+
/** Resolve a container node during enclosing-owner tree walks.
|
|
70
|
+
* Called when a CLASS_CONTAINER_TYPES node is found while walking up.
|
|
71
|
+
* - Return a different SyntaxNode to remap the container (e.g., Ruby
|
|
72
|
+
* singleton_class → enclosing class/module).
|
|
73
|
+
* - Return null to skip this container and keep walking up.
|
|
74
|
+
* - Omit (undefined) to use the container node as-is (default).
|
|
75
|
+
* Default: undefined (no remapping). */
|
|
76
|
+
readonly resolveEnclosingOwner?: (node: SyntaxNode) => SyntaxNode | null;
|
|
69
77
|
/** Resolve the enclosing function name + label from an AST ancestor node
|
|
70
78
|
* that is NOT a standard FUNCTION_NODE_TYPE. For languages where the
|
|
71
79
|
* function body is a sibling of the signature (e.g. Dart: function_body ↔
|
|
@@ -100,6 +100,21 @@ export const rubyProvider = defineLanguage({
|
|
|
100
100
|
importResolver: resolveRubyImport,
|
|
101
101
|
callRouter: routeRubyCall,
|
|
102
102
|
importSemantics: 'wildcard',
|
|
103
|
+
resolveEnclosingOwner(node) {
|
|
104
|
+
// Ruby singleton_class (class << self) should resolve to the enclosing
|
|
105
|
+
// class or module for owner/container resolution (HAS_METHOD edges, class IDs).
|
|
106
|
+
if (node.type === 'singleton_class') {
|
|
107
|
+
let ancestor = node.parent;
|
|
108
|
+
while (ancestor) {
|
|
109
|
+
if (ancestor.type === 'class' || ancestor.type === 'module') {
|
|
110
|
+
return ancestor;
|
|
111
|
+
}
|
|
112
|
+
ancestor = ancestor.parent;
|
|
113
|
+
}
|
|
114
|
+
return null; // no enclosing class/module — skip
|
|
115
|
+
}
|
|
116
|
+
return node; // use as-is for all other container types
|
|
117
|
+
},
|
|
103
118
|
fieldExtractor: createFieldExtractor(rubyFieldConfig),
|
|
104
119
|
methodExtractor: createMethodExtractor({
|
|
105
120
|
...rubyMethodConfig,
|
|
@@ -10,7 +10,7 @@ interface MdFile {
|
|
|
10
10
|
path: string;
|
|
11
11
|
content: string;
|
|
12
12
|
}
|
|
13
|
-
export declare const processMarkdown: (graph: KnowledgeGraph, files: MdFile[], allPathSet:
|
|
13
|
+
export declare const processMarkdown: (graph: KnowledgeGraph, files: MdFile[], allPathSet: ReadonlySet<string>) => {
|
|
14
14
|
sections: number;
|
|
15
15
|
links: number;
|
|
16
16
|
};
|
|
@@ -248,6 +248,7 @@ export const kotlinMethodConfig = {
|
|
|
248
248
|
typeDeclarationNodes: ['class_declaration', 'object_declaration', 'companion_object'],
|
|
249
249
|
methodNodeTypes: ['function_declaration'],
|
|
250
250
|
bodyNodeTypes: ['class_body'],
|
|
251
|
+
staticOwnerTypes: new Set(['companion_object', 'object_declaration']),
|
|
251
252
|
extractName(node) {
|
|
252
253
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
253
254
|
const child = node.namedChild(i);
|
|
@@ -202,6 +202,7 @@ export const rubyMethodConfig = {
|
|
|
202
202
|
typeDeclarationNodes: ['class', 'module', 'singleton_class'],
|
|
203
203
|
methodNodeTypes: ['method', 'singleton_method'],
|
|
204
204
|
bodyNodeTypes: ['body_statement'],
|
|
205
|
+
staticOwnerTypes: new Set(['singleton_class']),
|
|
205
206
|
extractOwnerName(node) {
|
|
206
207
|
// singleton_class (class << self) inherits the enclosing class/module name
|
|
207
208
|
if (node.type === 'singleton_class') {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { MethodExtractor, MethodExtractionConfig } from '../method-types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Create a MethodExtractor from a declarative config.
|
|
4
|
+
*
|
|
5
|
+
* @throws {Error} if `typeDeclarationNodes` contains a static-implying owner
|
|
6
|
+
* type (companion_object / object_declaration / singleton_class) that is
|
|
7
|
+
* not covered by `staticOwnerTypes`. The guard fires once per language at
|
|
8
|
+
* provider construction to prevent silent `isStatic=false` regressions. See
|
|
9
|
+
* `STATIC_IMPLYING_OWNER_TYPES` for the exact opt-out convention.
|
|
4
10
|
*/
|
|
5
11
|
export declare function createMethodExtractor(config: MethodExtractionConfig): MethodExtractor;
|
|
@@ -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
|
*
|