gitnexus 1.6.2-rc.9 → 1.6.2
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/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +1 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/mro-strategy.d.ts +38 -16
- package/dist/_shared/mro-strategy.d.ts.map +1 -1
- package/dist/cli/ai-context.js +0 -58
- package/dist/cli/analyze.js +3 -0
- package/dist/core/embeddings/ast-utils.d.ts +22 -0
- package/dist/core/embeddings/ast-utils.js +105 -0
- package/dist/core/embeddings/character-chunk.d.ts +12 -0
- package/dist/core/embeddings/character-chunk.js +43 -0
- package/dist/core/embeddings/chunker.d.ts +14 -0
- package/dist/core/embeddings/chunker.js +234 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +20 -24
- package/dist/core/embeddings/embedding-pipeline.js +176 -107
- package/dist/core/embeddings/line-index.d.ts +7 -0
- package/dist/core/embeddings/line-index.js +42 -0
- package/dist/core/embeddings/server-mapping.d.ts +15 -0
- package/dist/core/embeddings/server-mapping.js +33 -0
- package/dist/core/embeddings/structural-extractor.d.ts +15 -0
- package/dist/core/embeddings/structural-extractor.js +58 -0
- package/dist/core/embeddings/text-generator.d.ts +20 -13
- package/dist/core/embeddings/text-generator.js +151 -119
- package/dist/core/embeddings/types.d.ts +81 -3
- package/dist/core/embeddings/types.js +105 -3
- package/dist/core/group/extractors/http-patterns/node.js +130 -0
- package/dist/core/ingestion/call-extractors/configs/c-cpp.d.ts +3 -0
- package/dist/core/ingestion/call-extractors/configs/c-cpp.js +8 -0
- package/dist/core/ingestion/call-extractors/configs/csharp.d.ts +2 -0
- package/dist/core/ingestion/call-extractors/configs/csharp.js +6 -0
- package/dist/core/ingestion/call-extractors/configs/dart.d.ts +2 -0
- package/dist/core/ingestion/call-extractors/configs/dart.js +5 -0
- package/dist/core/ingestion/call-extractors/configs/go.d.ts +2 -0
- package/dist/core/ingestion/call-extractors/configs/go.js +5 -0
- package/dist/core/ingestion/call-extractors/configs/jvm.d.ts +3 -0
- package/dist/core/ingestion/call-extractors/configs/jvm.js +51 -0
- package/dist/core/ingestion/call-extractors/configs/php.d.ts +2 -0
- package/dist/core/ingestion/call-extractors/configs/php.js +5 -0
- package/dist/core/ingestion/call-extractors/configs/python.d.ts +2 -0
- package/dist/core/ingestion/call-extractors/configs/python.js +5 -0
- package/dist/core/ingestion/call-extractors/configs/ruby.d.ts +2 -0
- package/dist/core/ingestion/call-extractors/configs/ruby.js +5 -0
- package/dist/core/ingestion/call-extractors/configs/rust.d.ts +2 -0
- package/dist/core/ingestion/call-extractors/configs/rust.js +5 -0
- package/dist/core/ingestion/call-extractors/configs/swift.d.ts +2 -0
- package/dist/core/ingestion/call-extractors/configs/swift.js +5 -0
- package/dist/core/ingestion/call-extractors/configs/typescript-javascript.d.ts +3 -0
- package/dist/core/ingestion/call-extractors/configs/typescript-javascript.js +8 -0
- package/dist/core/ingestion/call-extractors/generic.d.ts +5 -0
- package/dist/core/ingestion/call-extractors/generic.js +59 -0
- package/dist/core/ingestion/call-processor.d.ts +2 -4
- package/dist/core/ingestion/call-processor.js +221 -89
- package/dist/core/ingestion/call-routing.d.ts +8 -12
- package/dist/core/ingestion/call-routing.js +13 -34
- package/dist/core/ingestion/call-types.d.ts +135 -0
- package/dist/core/ingestion/call-types.js +2 -0
- package/dist/core/ingestion/class-extractors/configs/c-cpp.d.ts +3 -0
- package/dist/core/ingestion/class-extractors/configs/c-cpp.js +11 -0
- package/dist/core/ingestion/class-extractors/configs/csharp.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/configs/csharp.js +21 -0
- package/dist/core/ingestion/class-extractors/configs/dart.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/configs/dart.js +7 -0
- package/dist/core/ingestion/class-extractors/configs/go.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/configs/go.js +20 -0
- package/dist/core/ingestion/class-extractors/configs/jvm.d.ts +3 -0
- package/dist/core/ingestion/class-extractors/configs/jvm.js +35 -0
- package/dist/core/ingestion/class-extractors/configs/php.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/configs/php.js +7 -0
- package/dist/core/ingestion/class-extractors/configs/python.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/configs/python.js +7 -0
- package/dist/core/ingestion/class-extractors/configs/ruby.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/configs/ruby.js +7 -0
- package/dist/core/ingestion/class-extractors/configs/rust.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/configs/rust.js +7 -0
- package/dist/core/ingestion/class-extractors/configs/swift.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/configs/swift.js +18 -0
- package/dist/core/ingestion/class-extractors/configs/typescript-javascript.d.ts +4 -0
- package/dist/core/ingestion/class-extractors/configs/typescript-javascript.js +28 -0
- package/dist/core/ingestion/field-types.d.ts +1 -1
- package/dist/core/ingestion/heritage-extractors/configs/go.d.ts +13 -0
- package/dist/core/ingestion/heritage-extractors/configs/go.js +20 -0
- package/dist/core/ingestion/heritage-extractors/configs/ruby.d.ts +18 -0
- package/dist/core/ingestion/heritage-extractors/configs/ruby.js +65 -0
- package/dist/core/ingestion/heritage-extractors/generic.d.ts +23 -0
- package/dist/core/ingestion/heritage-extractors/generic.js +47 -0
- package/dist/core/ingestion/heritage-processor.d.ts +9 -0
- package/dist/core/ingestion/heritage-processor.js +120 -85
- package/dist/core/ingestion/heritage-types.d.ts +73 -0
- package/dist/core/ingestion/heritage-types.js +2 -0
- package/dist/core/ingestion/import-resolvers/configs/c-cpp.d.ts +7 -0
- package/dist/core/ingestion/import-resolvers/configs/c-cpp.js +14 -0
- package/dist/core/ingestion/import-resolvers/configs/csharp.d.ts +8 -0
- package/dist/core/ingestion/import-resolvers/configs/csharp.js +27 -0
- package/dist/core/ingestion/import-resolvers/configs/dart.d.ts +17 -0
- package/dist/core/ingestion/import-resolvers/{dart.js → configs/dart.js} +26 -16
- package/dist/core/ingestion/import-resolvers/configs/go.d.ts +8 -0
- package/dist/core/ingestion/import-resolvers/configs/go.js +26 -0
- package/dist/core/ingestion/import-resolvers/configs/jvm.d.ts +13 -0
- package/dist/core/ingestion/import-resolvers/configs/jvm.js +68 -0
- package/dist/core/ingestion/import-resolvers/configs/php.d.ts +8 -0
- package/dist/core/ingestion/import-resolvers/configs/php.js +15 -0
- package/dist/core/ingestion/import-resolvers/configs/python.d.ts +12 -0
- package/dist/core/ingestion/import-resolvers/configs/python.js +41 -0
- package/dist/core/ingestion/import-resolvers/configs/ruby.d.ts +8 -0
- package/dist/core/ingestion/import-resolvers/configs/ruby.js +16 -0
- package/dist/core/ingestion/import-resolvers/configs/rust.d.ts +8 -0
- package/dist/core/ingestion/import-resolvers/configs/rust.js +54 -0
- package/dist/core/ingestion/import-resolvers/configs/swift.d.ts +8 -0
- package/dist/core/ingestion/import-resolvers/{swift.js → configs/swift.js} +10 -5
- package/dist/core/ingestion/import-resolvers/configs/typescript-javascript.d.ts +9 -0
- package/dist/core/ingestion/import-resolvers/configs/typescript-javascript.js +23 -0
- package/dist/core/ingestion/import-resolvers/csharp.d.ts +4 -5
- package/dist/core/ingestion/import-resolvers/csharp.js +4 -20
- package/dist/core/ingestion/import-resolvers/go.d.ts +4 -5
- package/dist/core/ingestion/import-resolvers/go.js +4 -19
- package/dist/core/ingestion/import-resolvers/jvm.d.ts +5 -10
- package/dist/core/ingestion/import-resolvers/jvm.js +5 -58
- package/dist/core/ingestion/import-resolvers/php.d.ts +4 -5
- package/dist/core/ingestion/import-resolvers/php.js +4 -7
- package/dist/core/ingestion/import-resolvers/python.d.ts +3 -6
- package/dist/core/ingestion/import-resolvers/python.js +3 -18
- package/dist/core/ingestion/import-resolvers/resolver-factory.d.ts +24 -0
- package/dist/core/ingestion/import-resolvers/resolver-factory.js +33 -0
- package/dist/core/ingestion/import-resolvers/ruby.d.ts +4 -5
- package/dist/core/ingestion/import-resolvers/ruby.js +4 -7
- package/dist/core/ingestion/import-resolvers/rust.d.ts +4 -5
- package/dist/core/ingestion/import-resolvers/rust.js +4 -47
- package/dist/core/ingestion/import-resolvers/standard.d.ts +3 -9
- package/dist/core/ingestion/import-resolvers/standard.js +7 -8
- package/dist/core/ingestion/import-resolvers/types.d.ts +24 -0
- package/dist/core/ingestion/language-provider.d.ts +80 -0
- package/dist/core/ingestion/languages/c-cpp.js +18 -12
- package/dist/core/ingestion/languages/csharp.js +13 -21
- package/dist/core/ingestion/languages/dart.js +13 -7
- package/dist/core/ingestion/languages/go.js +14 -20
- package/dist/core/ingestion/languages/java.js +13 -18
- package/dist/core/ingestion/languages/kotlin.js +13 -13
- package/dist/core/ingestion/languages/php.js +13 -7
- package/dist/core/ingestion/languages/python.js +13 -7
- package/dist/core/ingestion/languages/ruby.js +103 -22
- package/dist/core/ingestion/languages/rust.js +13 -7
- package/dist/core/ingestion/languages/swift.js +13 -18
- package/dist/core/ingestion/languages/typescript.js +18 -23
- package/dist/core/ingestion/languages/vue.js +13 -17
- package/dist/core/ingestion/model/heritage-map.d.ts +35 -0
- package/dist/core/ingestion/model/heritage-map.js +110 -9
- package/dist/core/ingestion/model/index.d.ts +2 -2
- package/dist/core/ingestion/model/index.js +1 -1
- package/dist/core/ingestion/model/resolve.d.ts +33 -28
- package/dist/core/ingestion/model/resolve.js +111 -27
- package/dist/core/ingestion/parsing-processor.d.ts +1 -2
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +9 -3
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +7 -0
- package/dist/core/ingestion/pipeline.d.ts +11 -0
- package/dist/core/ingestion/pipeline.js +9 -2
- package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
- package/dist/core/ingestion/tree-sitter-queries.js +81 -0
- package/dist/core/ingestion/type-env.d.ts +1 -1
- package/dist/core/ingestion/utils/ast-helpers.d.ts +1 -1
- package/dist/core/ingestion/utils/ast-helpers.js +22 -2
- package/dist/core/ingestion/utils/ruby-self-call.d.ts +52 -0
- package/dist/core/ingestion/utils/ruby-self-call.js +59 -0
- package/dist/core/ingestion/variable-extractors/configs/c-cpp.d.ts +3 -0
- package/dist/core/ingestion/variable-extractors/configs/c-cpp.js +81 -0
- package/dist/core/ingestion/variable-extractors/configs/csharp.d.ts +9 -0
- package/dist/core/ingestion/variable-extractors/configs/csharp.js +63 -0
- package/dist/core/ingestion/variable-extractors/configs/dart.d.ts +2 -0
- package/dist/core/ingestion/variable-extractors/configs/dart.js +94 -0
- package/dist/core/ingestion/variable-extractors/configs/go.d.ts +2 -0
- package/dist/core/ingestion/variable-extractors/configs/go.js +83 -0
- package/dist/core/ingestion/variable-extractors/configs/jvm.d.ts +18 -0
- package/dist/core/ingestion/variable-extractors/configs/jvm.js +115 -0
- package/dist/core/ingestion/variable-extractors/configs/php.d.ts +14 -0
- package/dist/core/ingestion/variable-extractors/configs/php.js +58 -0
- package/dist/core/ingestion/variable-extractors/configs/python.d.ts +2 -0
- package/dist/core/ingestion/variable-extractors/configs/python.js +101 -0
- package/dist/core/ingestion/variable-extractors/configs/ruby.d.ts +11 -0
- package/dist/core/ingestion/variable-extractors/configs/ruby.js +52 -0
- package/dist/core/ingestion/variable-extractors/configs/rust.d.ts +2 -0
- package/dist/core/ingestion/variable-extractors/configs/rust.js +76 -0
- package/dist/core/ingestion/variable-extractors/configs/swift.d.ts +2 -0
- package/dist/core/ingestion/variable-extractors/configs/swift.js +88 -0
- package/dist/core/ingestion/variable-extractors/configs/typescript-javascript.d.ts +3 -0
- package/dist/core/ingestion/variable-extractors/configs/typescript-javascript.js +83 -0
- package/dist/core/ingestion/variable-extractors/generic.d.ts +5 -0
- package/dist/core/ingestion/variable-extractors/generic.js +80 -0
- package/dist/core/ingestion/variable-types.d.ts +82 -0
- package/dist/core/ingestion/variable-types.js +2 -0
- package/dist/core/ingestion/workers/parse-worker.js +244 -217
- package/dist/core/ingestion/workers/worker-pool.js +3 -0
- package/dist/core/lbug/csv-generator.js +1 -0
- package/dist/core/lbug/lbug-adapter.d.ts +4 -5
- package/dist/core/lbug/lbug-adapter.js +38 -14
- package/dist/core/lbug/schema.d.ts +2 -1
- package/dist/core/lbug/schema.js +10 -1
- package/dist/core/run-analyze.js +6 -7
- package/dist/core/tree-sitter/parser-loader.d.ts +3 -0
- package/dist/core/tree-sitter/parser-loader.js +17 -8
- package/dist/mcp/local/local-backend.js +29 -19
- package/dist/server/api.js +2 -0
- package/dist/types/pipeline.d.ts +6 -0
- package/package.json +8 -7
- package/scripts/build-tree-sitter-proto.cjs +82 -0
- package/vendor/node_modules/node-addon-api/node_addon_api.Makefile +6 -0
- package/vendor/node_modules/node-addon-api/node_addon_api.target.mk +104 -0
- package/vendor/node_modules/node-addon-api/node_addon_api_except.target.mk +108 -0
- package/vendor/node_modules/node-addon-api/node_addon_api_except_all.target.mk +104 -0
- package/vendor/node_modules/node-addon-api/node_addon_api_maybe.target.mk +104 -0
- package/vendor/tree-sitter-proto/package.json +1 -7
- package/dist/core/ingestion/call-sites/extract-language-call-site.d.ts +0 -10
- package/dist/core/ingestion/call-sites/extract-language-call-site.js +0 -22
- package/dist/core/ingestion/call-sites/java.d.ts +0 -9
- package/dist/core/ingestion/call-sites/java.js +0 -30
- package/dist/core/ingestion/import-resolvers/dart.d.ts +0 -7
- package/dist/core/ingestion/import-resolvers/swift.d.ts +0 -7
- package/dist/core/ingestion/import-resolvers/vue.d.ts +0 -8
- package/dist/core/ingestion/import-resolvers/vue.js +0 -9
- package/scripts/preinstall-cleanup.cjs +0 -34
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
import { CLASS_TYPES, CALL_TARGET_TYPES } from './model/
|
|
1
|
+
import { CLASS_TYPES, CALL_TARGET_TYPES, lookupMethodByOwnerWithMRO } from './model/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* DAG stage 4 fallback: used when `selectDispatch` is absent or returns null.
|
|
4
|
+
* Preserves pre-DAG dispatch semantics:
|
|
5
|
+
* - 'constructor' → constructor branch
|
|
6
|
+
* - 'free' → free branch (admits Swift/Kotlin class-target fast path)
|
|
7
|
+
* - 'member' or undefined → owner-scoped branch
|
|
8
|
+
*
|
|
9
|
+
* `undefined` callForm MUST route through owner-scoped (not free) so bare
|
|
10
|
+
* identifiers without a classified shape do NOT trigger `resolveFreeCall`'s
|
|
11
|
+
* class-target fast path. Without a `receiverTypeName`, the owner-scoped
|
|
12
|
+
* branch falls through to `resolveModuleAliasedCall` + `singleCandidate`,
|
|
13
|
+
* matching legacy behavior where non-callable symbols (Class, Interface)
|
|
14
|
+
* null-route instead of producing spurious Constructor edges.
|
|
15
|
+
*/
|
|
16
|
+
const defaultDispatchDecision = (callForm) => {
|
|
17
|
+
if (callForm === 'constructor')
|
|
18
|
+
return { primary: 'constructor' };
|
|
19
|
+
if (callForm === 'free')
|
|
20
|
+
return { primary: 'free' };
|
|
21
|
+
return { primary: 'owner-scoped' };
|
|
22
|
+
};
|
|
2
23
|
import Parser from 'tree-sitter';
|
|
3
24
|
import { TIER_CONFIDENCE } from './model/resolution-context.js';
|
|
4
25
|
import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
|
|
@@ -15,8 +36,6 @@ import { getTreeSitterBufferSize } from './constants.js';
|
|
|
15
36
|
import { normalizeFetchURL, routeMatches } from './route-extractors/nextjs.js';
|
|
16
37
|
import { extractTemplateComponents } from './vue-sfc-extractor.js';
|
|
17
38
|
import { extractReturnTypeName, stripNullable } from './type-extractors/shared.js';
|
|
18
|
-
import { extractParsedCallSite } from './call-sites/extract-language-call-site.js';
|
|
19
|
-
import { lookupMethodByOwnerWithMRO } from './model/resolve.js';
|
|
20
39
|
/**
|
|
21
40
|
* Type labels treated as class-like **method-dispatch receivers** by the call
|
|
22
41
|
* resolver — the set walked by the MRO / heritage path for member and static
|
|
@@ -611,23 +630,27 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
|
|
|
611
630
|
// Extract heritage from query matches to build parentMap for buildTypeEnv.
|
|
612
631
|
// Heritage-processor runs in PARALLEL, so graph edges don't exist when buildTypeEnv runs.
|
|
613
632
|
const fileParentMap = new Map();
|
|
614
|
-
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
633
|
+
if (provider.heritageExtractor) {
|
|
634
|
+
for (const match of matches) {
|
|
635
|
+
const captureMap = {};
|
|
636
|
+
match.captures.forEach((c) => (captureMap[c.name] = c.node));
|
|
637
|
+
if (captureMap['heritage.class']) {
|
|
638
|
+
const heritageItems = provider.heritageExtractor.extract(captureMap, {
|
|
639
|
+
filePath: file.path,
|
|
640
|
+
language,
|
|
641
|
+
});
|
|
642
|
+
for (const item of heritageItems) {
|
|
643
|
+
if (item.kind === 'extends') {
|
|
644
|
+
let parents = fileParentMap.get(item.className);
|
|
645
|
+
if (!parents) {
|
|
646
|
+
parents = [];
|
|
647
|
+
fileParentMap.set(item.className, parents);
|
|
648
|
+
}
|
|
649
|
+
if (!parents.includes(item.parentName))
|
|
650
|
+
parents.push(item.parentName);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
628
653
|
}
|
|
629
|
-
if (!parents.includes(parentName))
|
|
630
|
-
parents.push(parentName);
|
|
631
654
|
}
|
|
632
655
|
}
|
|
633
656
|
const parentMap = fileParentMap;
|
|
@@ -733,73 +756,84 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
|
|
|
733
756
|
if (!captureMap['call'])
|
|
734
757
|
return;
|
|
735
758
|
const callNode = captureMap['call'];
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
(
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
759
|
+
const callExtractor = provider.callExtractor;
|
|
760
|
+
// ── Language-specific call site (e.g. Java :: method references) ──
|
|
761
|
+
if (callExtractor) {
|
|
762
|
+
const langCallSite = callExtractor.extract(callNode, undefined);
|
|
763
|
+
if (langCallSite) {
|
|
764
|
+
if (provider.isBuiltInName(langCallSite.calledName))
|
|
765
|
+
return;
|
|
766
|
+
const sourceId = findEnclosingFunction(callNode, file.path, ctx, provider) ||
|
|
767
|
+
generateId('File', file.path);
|
|
768
|
+
const receiverName = langCallSite.callForm === 'member' ? langCallSite.receiverName : undefined;
|
|
769
|
+
let receiverTypeName = receiverName && typeEnv ? typeEnv.lookup(receiverName, callNode) : undefined;
|
|
770
|
+
if (langCallSite.typeAsReceiverHeuristic &&
|
|
771
|
+
receiverName !== undefined &&
|
|
772
|
+
receiverTypeName === undefined &&
|
|
773
|
+
langCallSite.callForm === 'member') {
|
|
774
|
+
const c0 = receiverName.charCodeAt(0);
|
|
775
|
+
if (c0 >= 65 && c0 <= 90)
|
|
776
|
+
receiverTypeName = receiverName;
|
|
777
|
+
}
|
|
778
|
+
const resolved = resolveCallTarget({
|
|
779
|
+
calledName: langCallSite.calledName,
|
|
780
|
+
callForm: langCallSite.callForm,
|
|
781
|
+
...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
|
|
782
|
+
...(receiverName !== undefined ? { receiverName } : {}),
|
|
783
|
+
}, file.path, ctx, undefined, widenCache, undefined, heritageMap);
|
|
784
|
+
if (!resolved)
|
|
785
|
+
return;
|
|
786
|
+
graph.addRelationship({
|
|
787
|
+
id: generateId('CALLS', `${sourceId}:${langCallSite.calledName}->${resolved.nodeId}`),
|
|
788
|
+
sourceId,
|
|
789
|
+
targetId: resolved.nodeId,
|
|
790
|
+
type: 'CALLS',
|
|
791
|
+
confidence: resolved.confidence,
|
|
792
|
+
reason: resolved.reason,
|
|
793
|
+
});
|
|
794
|
+
if (heritageMap && langCallSite.callForm === 'member' && receiverTypeName) {
|
|
795
|
+
const implTargets = findInterfaceDispatchTargets(langCallSite.calledName, receiverTypeName, file.path, ctx, heritageMap, resolved.nodeId);
|
|
796
|
+
for (const impl of implTargets) {
|
|
797
|
+
graph.addRelationship({
|
|
798
|
+
id: generateId('CALLS', `${sourceId}:${langCallSite.calledName}->${impl.nodeId}`),
|
|
799
|
+
sourceId,
|
|
800
|
+
targetId: impl.nodeId,
|
|
801
|
+
type: 'CALLS',
|
|
802
|
+
confidence: impl.confidence,
|
|
803
|
+
reason: impl.reason,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
779
806
|
}
|
|
807
|
+
return;
|
|
780
808
|
}
|
|
781
|
-
return;
|
|
782
809
|
}
|
|
783
810
|
const nameNode = captureMap['call.name'];
|
|
784
811
|
if (!nameNode)
|
|
785
812
|
return;
|
|
786
813
|
const calledName = nameNode.text;
|
|
814
|
+
// Check heritage extractor for call-based heritage (e.g., Ruby include/extend/prepend)
|
|
815
|
+
if (provider.heritageExtractor?.extractFromCall) {
|
|
816
|
+
const heritageItems = provider.heritageExtractor.extractFromCall(calledName, captureMap['call'], { filePath: file.path, language });
|
|
817
|
+
if (heritageItems !== null) {
|
|
818
|
+
for (const item of heritageItems) {
|
|
819
|
+
collectedHeritage.push({
|
|
820
|
+
filePath: file.path,
|
|
821
|
+
className: item.className,
|
|
822
|
+
parentName: item.parentName,
|
|
823
|
+
kind: item.kind,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
// Dispatch: route language-specific calls (properties, imports)
|
|
830
|
+
// Heritage routing is handled by heritageExtractor.extractFromCall above.
|
|
787
831
|
const routed = callRouter?.(calledName, captureMap['call']);
|
|
788
832
|
if (routed) {
|
|
789
833
|
switch (routed.kind) {
|
|
790
834
|
case 'skip':
|
|
791
835
|
case 'import':
|
|
792
836
|
return;
|
|
793
|
-
case 'heritage':
|
|
794
|
-
for (const item of routed.items) {
|
|
795
|
-
collectedHeritage.push({
|
|
796
|
-
filePath: file.path,
|
|
797
|
-
className: item.enclosingClass,
|
|
798
|
-
parentName: item.mixinName,
|
|
799
|
-
kind: item.heritageKind,
|
|
800
|
-
});
|
|
801
|
-
}
|
|
802
|
-
return;
|
|
803
837
|
case 'properties': {
|
|
804
838
|
const fileId = generateId('File', file.path);
|
|
805
839
|
const propEnclosingClassId = findEnclosingClassId(captureMap['call'], file.path);
|
|
@@ -850,9 +884,16 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
|
|
|
850
884
|
}
|
|
851
885
|
if (provider.isBuiltInName(calledName))
|
|
852
886
|
return;
|
|
853
|
-
|
|
854
|
-
|
|
887
|
+
// --- DAG stage 2-3: classify-form + infer-receiver (shared defaults) ---
|
|
888
|
+
// These stages run the shared inference chain. Language providers can
|
|
889
|
+
// customize infer-receiver (stage 3) via the inferImplicitReceiver hook
|
|
890
|
+
// which runs AFTER this default chain (typed-binding → constructor-map →
|
|
891
|
+
// module-alias → class-as-receiver → mixed-chain), and selectDispatch
|
|
892
|
+
// (stage 4) which picks the resolver branch.
|
|
893
|
+
let callForm = inferCallForm(callNode, nameNode);
|
|
894
|
+
let receiverName = callForm === 'member' ? extractReceiverName(nameNode) : undefined;
|
|
855
895
|
let receiverTypeName = receiverName && typeEnv ? typeEnv.lookup(receiverName, callNode) : undefined;
|
|
896
|
+
let receiverSource = receiverTypeName ? 'typed-binding' : 'none';
|
|
856
897
|
// Phase P: virtual dispatch override — when the declared type is a base class but
|
|
857
898
|
// the constructor created a known subclass, prefer the more specific type.
|
|
858
899
|
// Checks per-file parentMap first, then falls back to globalParentMap for
|
|
@@ -888,6 +929,7 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
|
|
|
888
929
|
(ctx.model.types.lookupClassByName(ctorType).length > 0 &&
|
|
889
930
|
ctx.model.types.lookupClassByName(receiverTypeName).length > 0)) {
|
|
890
931
|
receiverTypeName = ctorType;
|
|
932
|
+
receiverSource = 'constructor-map';
|
|
891
933
|
}
|
|
892
934
|
}
|
|
893
935
|
}
|
|
@@ -896,18 +938,25 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
|
|
|
896
938
|
const enclosingFunc = findEnclosingFunction(callNode, file.path, ctx, provider);
|
|
897
939
|
const funcName = enclosingFunc ? extractFuncNameFromSourceId(enclosingFunc) : '';
|
|
898
940
|
receiverTypeName = lookupReceiverType(receiverIndex, funcName, receiverName);
|
|
941
|
+
if (receiverTypeName)
|
|
942
|
+
receiverSource = 'constructor-map';
|
|
899
943
|
}
|
|
900
|
-
// Fall back to class-as-receiver for static method calls (e.g. UserService.find_user()
|
|
901
|
-
// When the receiver name is not a variable in TypeEnv but
|
|
902
|
-
//
|
|
944
|
+
// Fall back to class-as-receiver for static method calls (e.g. UserService.find_user(),
|
|
945
|
+
// Greetable.format()). When the receiver name is not a variable in TypeEnv but
|
|
946
|
+
// resolves to a class-like symbol (Class / Interface / Struct / Enum / Trait) via
|
|
947
|
+
// tiered resolution, use it directly as the receiver type. `Trait` is included so
|
|
948
|
+
// Ruby module class-method calls flow through the class-as-receiver path and reach
|
|
949
|
+
// the `selectDispatch` hook's singleton branch.
|
|
903
950
|
if (!receiverTypeName && receiverName && callForm === 'member') {
|
|
904
951
|
const typeResolved = ctx.resolve(receiverName, file.path);
|
|
905
952
|
if (typeResolved &&
|
|
906
953
|
typeResolved.candidates.some((d) => d.type === 'Class' ||
|
|
907
954
|
d.type === 'Interface' ||
|
|
908
955
|
d.type === 'Struct' ||
|
|
909
|
-
d.type === 'Enum'
|
|
956
|
+
d.type === 'Enum' ||
|
|
957
|
+
d.type === 'Trait')) {
|
|
910
958
|
receiverTypeName = receiverName;
|
|
959
|
+
receiverSource = 'class-as-receiver';
|
|
911
960
|
}
|
|
912
961
|
}
|
|
913
962
|
// Hoist sourceId so it's available for ACCESSES edge emission during chain walk.
|
|
@@ -939,10 +988,48 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
|
|
|
939
988
|
}
|
|
940
989
|
if (currentType) {
|
|
941
990
|
receiverTypeName = walkMixedChain(extracted.chain, currentType, file.path, ctx, makeAccessEmitter(graph, sourceId), heritageMap);
|
|
991
|
+
if (receiverTypeName)
|
|
992
|
+
receiverSource = 'mixed-chain';
|
|
942
993
|
}
|
|
943
994
|
}
|
|
944
995
|
}
|
|
945
996
|
}
|
|
997
|
+
// --- DAG stage 3: infer-receiver (provider hook) ---
|
|
998
|
+
// Synthesize implicit receivers for languages that omit them (e.g., Ruby bare-call).
|
|
999
|
+
// This hook runs AFTER the shared inference chain so explicit receivers /
|
|
1000
|
+
// typed bindings always take precedence. Output (if non-null) overlays onto
|
|
1001
|
+
// the ReceiverEnriched for the next stage.
|
|
1002
|
+
let dispatchHint;
|
|
1003
|
+
if (provider.inferImplicitReceiver) {
|
|
1004
|
+
const override = provider.inferImplicitReceiver({
|
|
1005
|
+
calledName,
|
|
1006
|
+
callForm,
|
|
1007
|
+
receiverName,
|
|
1008
|
+
receiverTypeName,
|
|
1009
|
+
callNode,
|
|
1010
|
+
filePath: file.path,
|
|
1011
|
+
});
|
|
1012
|
+
if (override) {
|
|
1013
|
+
callForm = override.callForm;
|
|
1014
|
+
receiverName = override.receiverName;
|
|
1015
|
+
receiverTypeName = override.receiverTypeName;
|
|
1016
|
+
receiverSource = override.receiverSource;
|
|
1017
|
+
dispatchHint = override.hint;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
// --- DAG stage 4: select-dispatch (provider hook + default fallback) ---
|
|
1021
|
+
// Decide which resolver path to try first (primary) and fallback strategy.
|
|
1022
|
+
// Language providers can customize dispatch via selectDispatch hook; all
|
|
1023
|
+
// others use the shared defaultDispatchDecision. Always non-null after this
|
|
1024
|
+
// block so downstream resolvers are table-driven.
|
|
1025
|
+
const dispatchDecision = provider.selectDispatch?.({
|
|
1026
|
+
calledName,
|
|
1027
|
+
callForm,
|
|
1028
|
+
receiverName,
|
|
1029
|
+
receiverTypeName,
|
|
1030
|
+
receiverSource,
|
|
1031
|
+
hint: dispatchHint,
|
|
1032
|
+
}) ?? defaultDispatchDecision(callForm);
|
|
946
1033
|
// Build overload hints for languages with inferLiteralType (Java/Kotlin/C#/C++).
|
|
947
1034
|
// Only used when multiple candidates survive arity filtering — ~1-3% of calls.
|
|
948
1035
|
const langConfig = provider.typeConfig;
|
|
@@ -955,7 +1042,7 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
|
|
|
955
1042
|
callForm,
|
|
956
1043
|
receiverTypeName,
|
|
957
1044
|
receiverName,
|
|
958
|
-
}, file.path, ctx, hints, widenCache, undefined, heritageMap);
|
|
1045
|
+
}, file.path, ctx, hints, widenCache, undefined, heritageMap, dispatchDecision);
|
|
959
1046
|
if (!resolved)
|
|
960
1047
|
return;
|
|
961
1048
|
const relId = generateId('CALLS', `${sourceId}:${calledName}->${resolved.nodeId}`);
|
|
@@ -1357,26 +1444,45 @@ const singleCandidate = (tiered, argCount, callForm) => {
|
|
|
1357
1444
|
};
|
|
1358
1445
|
/** @internal Exported for unit tests. Do not use outside tests. */
|
|
1359
1446
|
export const _resolveCallTargetForTesting = (call, currentFile, ctx, opts) => resolveCallTarget(call, currentFile, ctx, opts?.overloadHints, opts?.widenCache, opts?.preComputedArgTypes, opts?.heritageMap);
|
|
1360
|
-
const resolveCallTarget = (call, currentFile, ctx, overloadHints, widenCache, preComputedArgTypes, heritageMap) => {
|
|
1447
|
+
const resolveCallTarget = (call, currentFile, ctx, overloadHints, widenCache, preComputedArgTypes, heritageMap, dispatchDecision) => {
|
|
1361
1448
|
const tiered = ctx.resolve(call.calledName, currentFile);
|
|
1362
1449
|
if (!tiered)
|
|
1363
1450
|
return null;
|
|
1364
|
-
|
|
1451
|
+
// DAG dispatch: use decision.primary to pick the resolver branch.
|
|
1452
|
+
// Callers that own the DAG (processCalls + crossFile deferred paths)
|
|
1453
|
+
// pass a decision; other callers use the shared default ladder.
|
|
1454
|
+
// Language-specific primary / fallback / ancestryView overrides come from
|
|
1455
|
+
// the provider's `selectDispatch` hook.
|
|
1456
|
+
const decision = dispatchDecision ?? defaultDispatchDecision(call.callForm);
|
|
1457
|
+
const primary = decision.primary;
|
|
1458
|
+
if (primary === 'free') {
|
|
1365
1459
|
return resolveFreeCall(call.calledName, currentFile, ctx, call.argCount, tiered, overloadHints, preComputedArgTypes);
|
|
1366
1460
|
}
|
|
1367
|
-
if (
|
|
1461
|
+
if (primary === 'constructor') {
|
|
1368
1462
|
return (resolveStaticCall(call.calledName, currentFile, ctx, call.argCount, tiered, overloadHints, preComputedArgTypes) ?? singleCandidate(tiered, call.argCount, 'constructor'));
|
|
1369
1463
|
}
|
|
1464
|
+
// primary === 'owner-scoped'
|
|
1370
1465
|
if (call.receiverTypeName) {
|
|
1371
1466
|
// Skip the owner-scoped MRO path when the tiered pool has genuine
|
|
1372
1467
|
// overload ambiguity that needs D1-D4+E handling, not D0.
|
|
1373
1468
|
const skipMember = (!!overloadHints || !!preComputedArgTypes) &&
|
|
1374
1469
|
countCallableCandidates(tiered.candidates, call.argCount, call.callForm) > 1;
|
|
1375
1470
|
// Try owner-scoped (resolveMemberCall) then file-scoped (resolveMemberCallByFile).
|
|
1471
|
+
// DAG: dispatchDecision.ancestryView selects instance vs singleton ancestry
|
|
1472
|
+
// for kind-aware MRO strategies. Ruby `Account.log` flows via 'singleton'.
|
|
1473
|
+
//
|
|
1474
|
+
// Singleton-ancestry miss MUST NOT degrade to the file-scoped fallback:
|
|
1475
|
+
// resolveMemberCallByFile matches by ownerId and would happily pick an
|
|
1476
|
+
// instance method defined on the same class, leaking instance dispatch
|
|
1477
|
+
// onto what was declared a class-method call. For singleton dispatch,
|
|
1478
|
+
// a miss either null-routes or falls through to `decision.fallback`.
|
|
1479
|
+
const singletonDispatch = decision.ancestryView === 'singleton';
|
|
1376
1480
|
const memberResult = (!skipMember
|
|
1377
|
-
? resolveMemberCall(call.receiverTypeName, call.calledName, currentFile, ctx, heritageMap, call.argCount)
|
|
1481
|
+
? resolveMemberCall(call.receiverTypeName, call.calledName, currentFile, ctx, heritageMap, call.argCount, decision.ancestryView)
|
|
1378
1482
|
: null) ??
|
|
1379
|
-
|
|
1483
|
+
(singletonDispatch
|
|
1484
|
+
? null
|
|
1485
|
+
: resolveMemberCallByFile(call.calledName, call.receiverTypeName, currentFile, ctx, call.argCount, call.callForm, overloadHints, preComputedArgTypes));
|
|
1380
1486
|
if (memberResult)
|
|
1381
1487
|
return memberResult;
|
|
1382
1488
|
// Module-alias narrowing runs as a FALLBACK, after owner/file-scoped
|
|
@@ -1409,7 +1515,19 @@ const resolveCallTarget = (call, currentFile, ctx, overloadHints, widenCache, pr
|
|
|
1409
1515
|
// hierarchy. When the type is NOT in the index (PHP `mixed`, dynamic
|
|
1410
1516
|
// types, unresolvable aliases), the scoped resolvers had nothing to
|
|
1411
1517
|
// work with and singleCandidate is the correct last resort.
|
|
1518
|
+
//
|
|
1519
|
+
// DAG fallback override: when `select-dispatch` returned
|
|
1520
|
+
// `fallback: 'free-arity-narrowed'` (today: Ruby implicit-self bare
|
|
1521
|
+
// calls whose enclosing class doesn't define the method), fall through
|
|
1522
|
+
// to free-call resolution instead of null-routing. This preserves
|
|
1523
|
+
// existing free-call arity-narrowing heuristics for bare calls that
|
|
1524
|
+
// happen to target methods on unrelated classes.
|
|
1412
1525
|
if (typeResolves && typeResolves.candidates.length > 0) {
|
|
1526
|
+
if (decision.fallback === 'free-arity-narrowed') {
|
|
1527
|
+
const free = resolveFreeCall(call.calledName, currentFile, ctx, call.argCount, tiered, overloadHints, preComputedArgTypes);
|
|
1528
|
+
if (free)
|
|
1529
|
+
return free;
|
|
1530
|
+
}
|
|
1413
1531
|
return null; // null-route: type resolved, no candidate matched
|
|
1414
1532
|
}
|
|
1415
1533
|
return singleCandidate(tiered, call.argCount, call.callForm);
|
|
@@ -1565,7 +1683,14 @@ const resolveFieldOwnership = (receiverName, fieldName, filePath, ctx) => {
|
|
|
1565
1683
|
* Threaded out here so callers don't need a second `ctx.resolve(ownerType, ...)` call —
|
|
1566
1684
|
* this decouples callers from `ctx.resolve`'s per-file caching contract.
|
|
1567
1685
|
*/
|
|
1568
|
-
const resolveMethodByOwner = (receiverTypeName, methodName, filePath, ctx, heritageMap, argCount
|
|
1686
|
+
const resolveMethodByOwner = (receiverTypeName, methodName, filePath, ctx, heritageMap, argCount,
|
|
1687
|
+
/**
|
|
1688
|
+
* DAG-sourced ancestry selector. `'singleton'` routes through
|
|
1689
|
+
* `heritageMap.getSingletonAncestry(owner)` for class-method dispatch
|
|
1690
|
+
* (Ruby `Account.log` via `extend LoggerMixin`). Default / undefined
|
|
1691
|
+
* uses the walker's instance-dispatch behavior.
|
|
1692
|
+
*/
|
|
1693
|
+
ancestryView) => {
|
|
1569
1694
|
const typeResolved = ctx.resolve(receiverTypeName, filePath);
|
|
1570
1695
|
if (!typeResolved)
|
|
1571
1696
|
return undefined;
|
|
@@ -1593,8 +1718,15 @@ const resolveMethodByOwner = (receiverTypeName, methodName, filePath, ctx, herit
|
|
|
1593
1718
|
for (const candidate of typeResolved.candidates) {
|
|
1594
1719
|
if (!CLASS_LIKE_TYPES.has(candidate.type))
|
|
1595
1720
|
continue;
|
|
1721
|
+
// Singleton dispatch: when the DAG decision requested the singleton
|
|
1722
|
+
// ancestry view, pass `heritageMap.getSingletonAncestry` as the walker's
|
|
1723
|
+
// ancestry override. Kind-aware strategies (e.g. MroStrategy 'ruby-mixin')
|
|
1724
|
+
// honor the override by scanning it linearly in place of their default walk.
|
|
1725
|
+
const singletonOverride = ancestryView === 'singleton' && canWalkMRO && heritageMap
|
|
1726
|
+
? heritageMap.getSingletonAncestry(candidate.nodeId).map((e) => e.parentId)
|
|
1727
|
+
: undefined;
|
|
1596
1728
|
const def = canWalkMRO
|
|
1597
|
-
? lookupMethodByOwnerWithMRO(candidate.nodeId, methodName, heritageMap, ctx.model, mroStrategy, argCount)
|
|
1729
|
+
? lookupMethodByOwnerWithMRO(candidate.nodeId, methodName, heritageMap, ctx.model, mroStrategy, argCount, singletonOverride)
|
|
1598
1730
|
: ctx.model.methods.lookupMethodByOwner(candidate.nodeId, methodName, argCount);
|
|
1599
1731
|
if (!def)
|
|
1600
1732
|
continue;
|
|
@@ -1641,8 +1773,8 @@ const resolveMethodByOwner = (receiverTypeName, methodName, filePath, ctx, herit
|
|
|
1641
1773
|
* @param ctx - Resolution context
|
|
1642
1774
|
* @param heritageMap - Optional heritage map for MRO-aware ancestor walking
|
|
1643
1775
|
*/
|
|
1644
|
-
export const resolveMemberCall = (ownerType, methodName, currentFile, ctx, heritageMap, argCount) => {
|
|
1645
|
-
const resolved = resolveMethodByOwner(ownerType, methodName, currentFile, ctx, heritageMap, argCount);
|
|
1776
|
+
export const resolveMemberCall = (ownerType, methodName, currentFile, ctx, heritageMap, argCount, ancestryView) => {
|
|
1777
|
+
const resolved = resolveMethodByOwner(ownerType, methodName, currentFile, ctx, heritageMap, argCount, ancestryView);
|
|
1646
1778
|
if (!resolved)
|
|
1647
1779
|
return null;
|
|
1648
1780
|
return toResolveResult(resolved.def, resolved.tier);
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared Ruby call routing logic.
|
|
3
3
|
*
|
|
4
|
-
* Ruby expresses imports
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Ruby expresses imports and property definitions as method calls rather
|
|
5
|
+
* than syntax-level constructs. This module provides a routing function
|
|
6
|
+
* used by the CLI call-processor, CLI parse-worker, and the web
|
|
7
|
+
* call-processor so that the classification logic lives in one place.
|
|
8
|
+
*
|
|
9
|
+
* Heritage (mixins: include/extend/prepend) was previously routed here
|
|
10
|
+
* but is now handled by heritageExtractor.extractFromCall before the
|
|
11
|
+
* call router runs. The router still returns 'skip' for these calls.
|
|
8
12
|
*
|
|
9
13
|
* NOTE: This file is intentionally duplicated in gitnexus-web/ because the
|
|
10
14
|
* two packages have separate build targets (Node native vs WASM/browser).
|
|
@@ -24,9 +28,6 @@ export type RubyCallRouting = {
|
|
|
24
28
|
kind: 'import';
|
|
25
29
|
importPath: string;
|
|
26
30
|
isRelative: boolean;
|
|
27
|
-
} | {
|
|
28
|
-
kind: 'heritage';
|
|
29
|
-
items: RubyHeritageItem[];
|
|
30
31
|
} | {
|
|
31
32
|
kind: 'properties';
|
|
32
33
|
items: RubyPropertyItem[];
|
|
@@ -35,11 +36,6 @@ export type RubyCallRouting = {
|
|
|
35
36
|
} | {
|
|
36
37
|
kind: 'skip';
|
|
37
38
|
};
|
|
38
|
-
export interface RubyHeritageItem {
|
|
39
|
-
enclosingClass: string;
|
|
40
|
-
mixinName: string;
|
|
41
|
-
heritageKind: 'include' | 'extend' | 'prepend';
|
|
42
|
-
}
|
|
43
39
|
export type RubyAccessorType = 'attr_accessor' | 'attr_reader' | 'attr_writer';
|
|
44
40
|
export interface RubyPropertyItem {
|
|
45
41
|
propName: string;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared Ruby call routing logic.
|
|
3
3
|
*
|
|
4
|
-
* Ruby expresses imports
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Ruby expresses imports and property definitions as method calls rather
|
|
5
|
+
* than syntax-level constructs. This module provides a routing function
|
|
6
|
+
* used by the CLI call-processor, CLI parse-worker, and the web
|
|
7
|
+
* call-processor so that the classification logic lives in one place.
|
|
8
|
+
*
|
|
9
|
+
* Heritage (mixins: include/extend/prepend) was previously routed here
|
|
10
|
+
* but is now handled by heritageExtractor.extractFromCall before the
|
|
11
|
+
* call router runs. The router still returns 'skip' for these calls.
|
|
8
12
|
*
|
|
9
13
|
* NOTE: This file is intentionally duplicated in gitnexus-web/ because the
|
|
10
14
|
* two packages have separate build targets (Node native vs WASM/browser).
|
|
@@ -13,8 +17,6 @@
|
|
|
13
17
|
// ── Pre-allocated singletons for common return values ────────────────────────
|
|
14
18
|
const CALL_RESULT = { kind: 'call' };
|
|
15
19
|
const SKIP_RESULT = { kind: 'skip' };
|
|
16
|
-
/** Max depth for parent-walking loops to prevent pathological AST traversals */
|
|
17
|
-
const MAX_PARENT_DEPTH = 50;
|
|
18
20
|
// ── Routing function ────────────────────────────────────────────────────────
|
|
19
21
|
/**
|
|
20
22
|
* Classify a Ruby call node and extract its semantic payload.
|
|
@@ -42,35 +44,12 @@ export function routeRubyCall(calledName, callNode) {
|
|
|
42
44
|
}
|
|
43
45
|
return { kind: 'import', importPath, isRelative };
|
|
44
46
|
}
|
|
45
|
-
// ── include / extend / prepend
|
|
47
|
+
// ── include / extend / prepend — heritage (now handled by heritageExtractor) ─
|
|
48
|
+
// Call-based heritage is intercepted by heritageExtractor.extractFromCall
|
|
49
|
+
// before the call router runs. Return SKIP_RESULT so these calls don't
|
|
50
|
+
// fall through to normal call processing.
|
|
46
51
|
if (calledName === 'include' || calledName === 'extend' || calledName === 'prepend') {
|
|
47
|
-
|
|
48
|
-
let current = callNode.parent;
|
|
49
|
-
let depth = 0;
|
|
50
|
-
while (current && ++depth <= MAX_PARENT_DEPTH) {
|
|
51
|
-
if (current.type === 'class' || current.type === 'module') {
|
|
52
|
-
const nameNode = current.childForFieldName?.('name');
|
|
53
|
-
if (nameNode) {
|
|
54
|
-
enclosingClass = nameNode.text;
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
current = current.parent;
|
|
59
|
-
}
|
|
60
|
-
if (!enclosingClass)
|
|
61
|
-
return SKIP_RESULT;
|
|
62
|
-
const items = [];
|
|
63
|
-
const argList = callNode.childForFieldName?.('arguments');
|
|
64
|
-
for (const arg of argList?.children ?? []) {
|
|
65
|
-
if (arg.type === 'constant' || arg.type === 'scope_resolution') {
|
|
66
|
-
items.push({
|
|
67
|
-
enclosingClass,
|
|
68
|
-
mixinName: arg.text,
|
|
69
|
-
heritageKind: calledName,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return items.length > 0 ? { kind: 'heritage', items } : SKIP_RESULT;
|
|
52
|
+
return SKIP_RESULT;
|
|
74
53
|
}
|
|
75
54
|
// ── attr_accessor / attr_reader / attr_writer → property definitions ───
|
|
76
55
|
if (calledName === 'attr_accessor' ||
|