gitnexus 1.6.4-rc.2 → 1.6.4-rc.21
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 +35 -0
- package/dist/_shared/index.d.ts +1 -1
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +1 -1
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +22 -14
- package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.js +298 -37
- package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts +23 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js +36 -2
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +47 -3
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/types.js +10 -2
- package/dist/_shared/scope-resolution/types.js.map +1 -1
- package/dist/cli/analyze.d.ts +6 -0
- package/dist/cli/analyze.js +35 -0
- package/dist/cli/doctor.d.ts +1 -0
- package/dist/cli/doctor.js +31 -0
- package/dist/cli/index.js +13 -0
- package/dist/cli/setup.js +2 -2
- package/dist/core/embeddings/config.d.ts +2 -0
- package/dist/core/embeddings/config.js +36 -0
- package/dist/core/embeddings/embedder.js +11 -6
- package/dist/core/embeddings/embedding-pipeline.d.ts +7 -1
- package/dist/core/embeddings/embedding-pipeline.js +93 -29
- package/dist/core/embeddings/exact-search.d.ts +15 -0
- package/dist/core/embeddings/exact-search.js +27 -0
- package/dist/core/embeddings/types.d.ts +4 -0
- package/dist/core/embeddings/types.js +2 -0
- package/dist/core/group/config-parser.js +2 -0
- package/dist/core/group/matching.d.ts +3 -3
- package/dist/core/group/matching.js +46 -6
- package/dist/core/group/storage.js +2 -0
- package/dist/core/group/sync.js +1 -1
- package/dist/core/group/types.d.ts +18 -0
- package/dist/core/ingestion/call-processor.d.ts +3 -3
- package/dist/core/ingestion/call-processor.js +58 -65
- package/dist/core/ingestion/constants.d.ts +4 -3
- package/dist/core/ingestion/constants.js +8 -3
- package/dist/core/ingestion/finalize-orchestrator.js +6 -3
- package/dist/core/ingestion/heritage-processor.js +2 -2
- package/dist/core/ingestion/import-processor.js +1 -1
- package/dist/core/ingestion/language-provider.d.ts +8 -0
- package/dist/core/ingestion/languages/csharp/captures.js +4 -1
- package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +14 -13
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +62 -50
- package/dist/core/ingestion/languages/python/captures.js +9 -1
- package/dist/core/ingestion/languages/python/index.d.ts +1 -1
- package/dist/core/ingestion/languages/python/index.js +1 -1
- package/dist/core/ingestion/languages/python/simple-hooks.d.ts +3 -1
- package/dist/core/ingestion/languages/python/simple-hooks.js +8 -0
- package/dist/core/ingestion/languages/python.js +28 -1
- package/dist/core/ingestion/languages/swift.js +14 -0
- package/dist/core/ingestion/languages/typescript/arity-metadata.d.ts +59 -0
- package/dist/core/ingestion/languages/typescript/arity-metadata.js +103 -0
- package/dist/core/ingestion/languages/typescript/arity.d.ts +37 -0
- package/dist/core/ingestion/languages/typescript/arity.js +54 -0
- package/dist/core/ingestion/languages/typescript/cache-stats.d.ts +17 -0
- package/dist/core/ingestion/languages/typescript/cache-stats.js +28 -0
- package/dist/core/ingestion/languages/typescript/captures.d.ts +28 -0
- package/dist/core/ingestion/languages/typescript/captures.js +451 -0
- package/dist/core/ingestion/languages/typescript/import-decomposer.d.ts +49 -0
- package/dist/core/ingestion/languages/typescript/import-decomposer.js +371 -0
- package/dist/core/ingestion/languages/typescript/import-target.d.ts +50 -0
- package/dist/core/ingestion/languages/typescript/import-target.js +61 -0
- package/dist/core/ingestion/languages/typescript/index.d.ts +94 -0
- package/dist/core/ingestion/languages/typescript/index.js +94 -0
- package/dist/core/ingestion/languages/typescript/interpret.d.ts +35 -0
- package/dist/core/ingestion/languages/typescript/interpret.js +317 -0
- package/dist/core/ingestion/languages/typescript/merge-bindings.d.ts +62 -0
- package/dist/core/ingestion/languages/typescript/merge-bindings.js +158 -0
- package/dist/core/ingestion/languages/typescript/query.d.ts +77 -0
- package/dist/core/ingestion/languages/typescript/query.js +778 -0
- package/dist/core/ingestion/languages/typescript/receiver-binding.d.ts +59 -0
- package/dist/core/ingestion/languages/typescript/receiver-binding.js +171 -0
- package/dist/core/ingestion/languages/typescript/scope-resolver.d.ts +16 -0
- package/dist/core/ingestion/languages/typescript/scope-resolver.js +113 -0
- package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +71 -0
- package/dist/core/ingestion/languages/typescript/simple-hooks.js +131 -0
- package/dist/core/ingestion/languages/typescript.js +19 -0
- package/dist/core/ingestion/method-extractors/configs/swift.js +3 -4
- package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +14 -1
- package/dist/core/ingestion/parsing-processor.js +20 -9
- package/dist/core/ingestion/pipeline-phases/processes.js +9 -4
- package/dist/core/ingestion/pipeline-phases/tools.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/tools.js +10 -4
- package/dist/core/ingestion/registry-primary-flag.d.ts +3 -1
- package/dist/core/ingestion/registry-primary-flag.js +4 -1
- package/dist/core/ingestion/scope-extractor-bridge.d.ts +5 -2
- package/dist/core/ingestion/scope-extractor-bridge.js +7 -2
- package/dist/core/ingestion/scope-extractor.js +19 -18
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +73 -11
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +48 -10
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +283 -14
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +23 -2
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +109 -37
- package/dist/core/ingestion/scope-resolution/passes/mro.js +3 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +13 -5
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +11 -2
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +8 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +21 -5
- package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.d.ts +39 -0
- package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +65 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +54 -11
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +105 -30
- package/dist/core/ingestion/type-extractors/swift.js +7 -4
- package/dist/core/ingestion/utils/ast-helpers.d.ts +2 -0
- package/dist/core/ingestion/utils/ast-helpers.js +12 -0
- package/dist/core/ingestion/utils/env.d.ts +10 -0
- package/dist/core/ingestion/utils/env.js +14 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
- package/dist/core/ingestion/workers/parse-worker.js +15 -9
- package/dist/core/ingestion/workers/worker-pool.d.ts +11 -4
- package/dist/core/ingestion/workers/worker-pool.js +244 -48
- package/dist/core/lbug/extension-loader.d.ts +86 -0
- package/dist/core/lbug/extension-loader.js +184 -0
- package/dist/core/lbug/lbug-adapter.d.ts +18 -17
- package/dist/core/lbug/lbug-adapter.js +45 -73
- package/dist/core/lbug/pool-adapter.js +10 -28
- package/dist/core/platform/capabilities.d.ts +24 -0
- package/dist/core/platform/capabilities.js +54 -0
- package/dist/core/run-analyze.js +36 -9
- package/dist/core/search/bm25-index.d.ts +0 -17
- package/dist/core/search/bm25-index.js +10 -118
- package/dist/core/search/fts-indexes.d.ts +1 -0
- package/dist/core/search/fts-indexes.js +7 -0
- package/dist/core/search/fts-schema.d.ts +6 -0
- package/dist/core/search/fts-schema.js +7 -0
- package/dist/mcp/core/embedder.js +11 -4
- package/dist/mcp/local/local-backend.js +50 -15
- package/dist/server/api.d.ts +5 -0
- package/dist/server/api.js +113 -0
- package/hooks/claude/gitnexus-hook.cjs +11 -1
- package/package.json +6 -5
- package/scripts/build-tree-sitter-dart.cjs +42 -0
- package/scripts/build-tree-sitter-proto.cjs +1 -1
- package/scripts/build.js +22 -2
- package/scripts/install-duckdb-extension.mjs +37 -0
- package/vendor/tree-sitter-dart/README.md +18 -0
- package/vendor/tree-sitter-dart/binding.gyp +31 -0
- package/vendor/tree-sitter-dart/bindings/node/binding.cc +20 -0
- package/vendor/tree-sitter-dart/bindings/node/index.d.ts +28 -0
- package/vendor/tree-sitter-dart/bindings/node/index.js +7 -0
- package/vendor/tree-sitter-dart/grammar.js +2895 -0
- package/vendor/tree-sitter-dart/package.json +18 -0
- package/vendor/tree-sitter-dart/queries/highlights.scm +246 -0
- package/vendor/tree-sitter-dart/queries/tags.scm +92 -0
- package/vendor/tree-sitter-dart/queries/test.scm +1 -0
- package/vendor/tree-sitter-dart/src/grammar.json +12459 -0
- package/vendor/tree-sitter-dart/src/node-types.json +15055 -0
- package/vendor/tree-sitter-dart/src/parser.c +196127 -0
- package/vendor/tree-sitter-dart/src/scanner.c +130 -0
- package/vendor/tree-sitter-dart/src/tree_sitter/alloc.h +54 -0
- package/vendor/tree-sitter-dart/src/tree_sitter/array.h +290 -0
- package/vendor/tree-sitter-dart/src/tree_sitter/parser.h +265 -0
- package/vendor/tree-sitter-swift/LICENSE +21 -0
- package/vendor/tree-sitter-swift/README.md +139 -0
- package/vendor/tree-sitter-swift/bindings/node/index.d.ts +28 -0
- package/vendor/tree-sitter-swift/bindings/node/index.js +7 -0
- package/vendor/tree-sitter-swift/package.json +28 -0
- package/vendor/tree-sitter-swift/prebuilds/darwin-arm64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/prebuilds/darwin-x64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/prebuilds/linux-arm64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/prebuilds/linux-x64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/prebuilds/win32-arm64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/prebuilds/win32-x64/tree-sitter-swift.node +0 -0
- package/vendor/tree-sitter-swift/src/node-types.json +30694 -0
- package/web/assets/agent-DaprsFSX.js +597 -0
- package/web/assets/architecture-YZFGNWBL-S5CXDPWN-DEdGaPg2.js +1 -0
- package/web/assets/architectureDiagram-EMZXCZ2Q-Domyk_gO.js +36 -0
- package/web/assets/blockDiagram-IGV67L2C-B_2kD7tM.js +132 -0
- package/web/assets/c4Diagram-DFAF54RM-BhJJW8Gg.js +10 -0
- package/web/assets/chunk-3GS5O3IE-jlWIjPsl.js +231 -0
- package/web/assets/chunk-3YCYZ6SJ-Blq_IzZs.js +1 -0
- package/web/assets/chunk-6NTNNK5N-DyPc58pp.js +1 -0
- package/web/assets/chunk-7RZVMHOQ-BdIU-RGO.js +321 -0
- package/web/assets/chunk-A34GCYZU-BI2i_LdU.js +1 -0
- package/web/assets/chunk-AEOMTBSW-D7qjBMHW.js +1 -0
- package/web/assets/chunk-CilyBKbf.js +1 -0
- package/web/assets/chunk-DJ7UZH7F-i11ywiBl.js +1 -0
- package/web/assets/chunk-DKKBVRCY-1SffGI1N.js +4 -0
- package/web/assets/chunk-DU5LTGQ6-DaPeiwD5.js +1 -0
- package/web/assets/chunk-FXACKDTF-uhhi2PC2.js +159 -0
- package/web/assets/chunk-H3VCZNTA-IchcISDt.js +1 -0
- package/web/assets/chunk-HN6EAY2L-D7ZFMNrB.js +1 -0
- package/web/assets/chunk-KSICW3F5-C2tZmXwv.js +15 -0
- package/web/assets/chunk-O5ABG6QK-Bt-Km84H.js +1 -0
- package/web/assets/chunk-PK6DOVAG-ChlWY0BQ.js +206 -0
- package/web/assets/chunk-RNJOYNJ4-B724K7cW.js +1 -0
- package/web/assets/chunk-RWUO3TPN-DYn1XriD.js +1 -0
- package/web/assets/chunk-TBF5ZNIQ-DKtDz6ae.js +1 -0
- package/web/assets/chunk-TU3PZOEN-DE5Qhc0N.js +1 -0
- package/web/assets/chunk-TYMNRAUI-g1h33cq-.js +1 -0
- package/web/assets/chunk-VELTKBKT-C9dVN39o.js +1 -0
- package/web/assets/chunk-W7ZLLLMY-Du-Hb9yb.js +1 -0
- package/web/assets/chunk-WSB5WSVC-B123clsZ.js +1 -0
- package/web/assets/chunk-XGPFEOL4-BR7Eue38.js +1 -0
- package/web/assets/classDiagram-PPOCWD7C-BglfKSs_.js +1 -0
- package/web/assets/classDiagram-v2-23LJLIIU-BSzTM28O.js +1 -0
- package/web/assets/context-builder-CqQNhRj1.js +15 -0
- package/web/assets/cose-bilkent-PNC4W37J-DCfErU-A.js +1 -0
- package/web/assets/dagre-E77IOHMT-tDRRhDoN.js +4 -0
- package/web/assets/diagram-H7BISOXX-CUVHlmAh.js +43 -0
- package/web/assets/diagram-JC5VWROH-BoyOxulB.js +24 -0
- package/web/assets/diagram-LXUTUG65-osr9hb7N.js +10 -0
- package/web/assets/diagram-WEHSV5V5-d8nUqS39.js +24 -0
- package/web/assets/erDiagram-GCSMX5X6-b-IwOhPS.js +85 -0
- package/web/assets/flowDiagram-OTCZ4VVT-Ott2Q0AP.js +162 -0
- package/web/assets/ganttDiagram-MUNLMDZQ-BYtgN_5s.js +292 -0
- package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-CFyBIGZq.js +1 -0
- package/web/assets/gitGraphDiagram-3HKGZ4G3-CsVD2gn4.js +106 -0
- package/web/assets/index-BleGLU8S.css +2 -0
- package/web/assets/index-C_xK08EW.js +885 -0
- package/web/assets/info-OMHHGYJF-BF2H5H6G-yjAxKEzh.js +1 -0
- package/web/assets/infoDiagram-MN7RKWGX-DXK0Unn5.js +2 -0
- package/web/assets/ishikawaDiagram-YMYX4NHK-CXsnC2FA.js +70 -0
- package/web/assets/journeyDiagram-SO5T7YLQ-BzZ07B-X.js +139 -0
- package/web/assets/kanban-definition-LJHFXRCJ-C6_EpAd9.js +89 -0
- package/web/assets/katex-GD7MH7QM-CJiOjBBJ.js +261 -0
- package/web/assets/mindmap-definition-2EUWGEK5-CCYGWZ1m.js +96 -0
- package/web/assets/packet-4T2RLAQJ-EV4IVRXR-B8k4E3IT.js +1 -0
- package/web/assets/pie-ZZUOXDRM-N23DN5KN-DdvfY118.js +1 -0
- package/web/assets/pieDiagram-3IATQBI2-RyvRlQb4.js +30 -0
- package/web/assets/quadrantDiagram-E256RVCF-Bfb6sxCx.js +7 -0
- package/web/assets/radar-PYXPWWZC-P6TP7ZYP-1EEDC_yU.js +1 -0
- package/web/assets/requirementDiagram-M5DCFWZL-DjvHDyvN.js +84 -0
- package/web/assets/sankeyDiagram-L3NBLAOT-CBCbbl8s.js +10 -0
- package/web/assets/sequenceDiagram-ZOUHS735-BscU8TUR.js +157 -0
- package/web/assets/stateDiagram-MLPALWAM-CJusEK2D.js +1 -0
- package/web/assets/stateDiagram-v2-B5LQ5ZB2-DImJ3PXD.js +1 -0
- package/web/assets/timeline-definition-5SPVSISX-DigPA1X8.js +120 -0
- package/web/assets/treeView-SZITEDCU-5DXDK3XO-CzPDt3aG.js +1 -0
- package/web/assets/treemap-W4RFUUIX-WYLRDWKO-B9Iqiorr.js +1 -0
- package/web/assets/vennDiagram-IE5QUKF5-C91UkZIf.js +34 -0
- package/web/assets/wardley-RL74JXVD-BCRCBASE-x42Qw7hp.js +1 -0
- package/web/assets/wardleyDiagram-XU3VSMPF-DloBhI0U.js +20 -0
- package/web/assets/xychartDiagram-ZHJ5623Y-BGWJvgwI.js +7 -0
- package/web/index.html +21 -0
- package/scripts/patch-tree-sitter-swift.cjs +0 -78
|
@@ -24,6 +24,16 @@ import { findClassBindingInScope, findExportedDefByName, findReceiverTypeBinding
|
|
|
24
24
|
* Practical code rarely exceeds 3-4 hops; the cap prevents
|
|
25
25
|
* pathological recursion if the receiver text is malformed. */
|
|
26
26
|
const COMPOUND_RECEIVER_MAX_DEPTH = 4;
|
|
27
|
+
const MAP_TUPLE_SENTINEL_RE = /^__MAP_TUPLE_(\d+)__:(.+)$/;
|
|
28
|
+
function parseMapTupleSentinel(text) {
|
|
29
|
+
const match = MAP_TUPLE_SENTINEL_RE.exec(text);
|
|
30
|
+
if (match === null)
|
|
31
|
+
return null;
|
|
32
|
+
const [, idxStr, rhs] = match;
|
|
33
|
+
if (idxStr === undefined || rhs === undefined)
|
|
34
|
+
return null;
|
|
35
|
+
return { tupleIdx: Number(idxStr), rhs };
|
|
36
|
+
}
|
|
27
37
|
export function resolveCompoundReceiverClass(receiverText, inScope, scopes, index, options = {}, depth = 0) {
|
|
28
38
|
const classScopeByDefId = index.classScopeByDefId;
|
|
29
39
|
if (depth > COMPOUND_RECEIVER_MAX_DEPTH)
|
|
@@ -32,12 +42,57 @@ export function resolveCompoundReceiverClass(receiverText, inScope, scopes, inde
|
|
|
32
42
|
if (text.length === 0)
|
|
33
43
|
return undefined;
|
|
34
44
|
const fieldFallback = options.fieldFallback ?? true;
|
|
35
|
-
// Bare identifier — resolve via typeBinding then
|
|
45
|
+
// Bare identifier — resolve via typeBinding first, then fall back to
|
|
46
|
+
// a direct class-name lookup. The class-name fallback handles
|
|
47
|
+
// "static receiver" shapes like `UserService.findUser()` where
|
|
48
|
+
// `UserService` isn't a variable but a class imported into scope.
|
|
36
49
|
if (!text.includes('.') && !text.includes('(')) {
|
|
50
|
+
const mapTuple = parseMapTupleSentinel(text);
|
|
51
|
+
if (mapTuple !== null) {
|
|
52
|
+
const rhsTb = findReceiverTypeBinding(inScope, mapTuple.rhs, scopes);
|
|
53
|
+
if (rhsTb === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
const arg = extractShallowMapTypeArgByIndex(rhsTb.rawName, mapTuple.tupleIdx);
|
|
56
|
+
if (arg === undefined)
|
|
57
|
+
return undefined;
|
|
58
|
+
return findClassBindingInScope(rhsTb.declaredAtScope, arg, scopes);
|
|
59
|
+
}
|
|
37
60
|
const tb = findReceiverTypeBinding(inScope, text, scopes);
|
|
38
|
-
if (tb
|
|
39
|
-
|
|
40
|
-
|
|
61
|
+
if (tb !== undefined) {
|
|
62
|
+
// Map for-of: binding name is `user` but rawType is
|
|
63
|
+
// `__MAP_TUPLE_i__:entries` (see captures.ts) — same extraction as
|
|
64
|
+
// the literal-sentinel branch above.
|
|
65
|
+
const boundMapTuple = parseMapTupleSentinel(tb.rawName);
|
|
66
|
+
if (boundMapTuple !== null) {
|
|
67
|
+
const rhsTb = findReceiverTypeBinding(inScope, boundMapTuple.rhs, scopes);
|
|
68
|
+
if (rhsTb === undefined)
|
|
69
|
+
return undefined;
|
|
70
|
+
const arg = extractShallowMapTypeArgByIndex(rhsTb.rawName, boundMapTuple.tupleIdx);
|
|
71
|
+
if (arg === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
return findClassBindingInScope(rhsTb.declaredAtScope, arg, scopes);
|
|
74
|
+
}
|
|
75
|
+
const viaTb = findClassBindingInScope(tb.declaredAtScope, tb.rawName, scopes);
|
|
76
|
+
if (viaTb !== undefined)
|
|
77
|
+
return viaTb;
|
|
78
|
+
// Member-alias / call-result shapes store the RHS path on rawName
|
|
79
|
+
// (`user.address`, `addr.getCity`) — resolve as a compound chain.
|
|
80
|
+
if (tb.rawName.includes('.') && !tb.rawName.includes('(')) {
|
|
81
|
+
const dotted = resolveCompoundReceiverClass(tb.rawName, inScope, scopes, index, options, depth + 1);
|
|
82
|
+
if (dotted !== undefined)
|
|
83
|
+
return dotted;
|
|
84
|
+
const dottedCall = resolveCompoundReceiverClass(`${tb.rawName}()`, inScope, scopes, index, options, depth + 1);
|
|
85
|
+
if (dottedCall !== undefined)
|
|
86
|
+
return dottedCall;
|
|
87
|
+
}
|
|
88
|
+
// Callable alias (`const user = getUser()` → type rawName `getUser`)
|
|
89
|
+
if (!tb.rawName.includes('.') && !tb.rawName.includes('(')) {
|
|
90
|
+
const callAlias = resolveCompoundReceiverClass(`${tb.rawName}()`, inScope, scopes, index, options, depth + 1);
|
|
91
|
+
if (callAlias !== undefined)
|
|
92
|
+
return callAlias;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return findClassBindingInScope(inScope, text, scopes);
|
|
41
96
|
}
|
|
42
97
|
// Trailing `()` — call expression. Strip it and resolve the function
|
|
43
98
|
// expression's return type. We only handle the canonical `f()` /
|
|
@@ -120,12 +175,33 @@ export function resolveCompoundReceiverClass(receiverText, inScope, scopes, inde
|
|
|
120
175
|
}
|
|
121
176
|
}
|
|
122
177
|
}
|
|
178
|
+
// `Map<K,V>.values()` / `this.repos.values()` — lib `Map` often has no
|
|
179
|
+
// parsed return-type binding; infer `V` from the receiver field's
|
|
180
|
+
// `Map<…>` annotation when the method is `values`.
|
|
181
|
+
if (retType === undefined && methodName === 'values') {
|
|
182
|
+
const mapVal = resolveMapValueTypeNameFromPrefix(objExpr, inScope, scopes, index, options);
|
|
183
|
+
if (mapVal !== undefined) {
|
|
184
|
+
retType = {
|
|
185
|
+
rawName: mapVal,
|
|
186
|
+
declaredAtScope: inScope,
|
|
187
|
+
source: 'return-annotation',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
123
191
|
if (retType === undefined)
|
|
124
192
|
return undefined;
|
|
125
193
|
return findClassBindingInScope(retType.declaredAtScope, retType.rawName, scopes);
|
|
126
194
|
}
|
|
127
|
-
//
|
|
128
|
-
|
|
195
|
+
// Mixed dotted + call chain: `obj.field.method().field.method()…`.
|
|
196
|
+
// Split at top-level `.` (those NOT inside balanced `(...)`) so a
|
|
197
|
+
// middle segment like `getUser()` stays intact. Each segment is
|
|
198
|
+
// either a bare identifier `field` OR `method(...)` — the former
|
|
199
|
+
// resolves via the current class's typeBindings (field → type),
|
|
200
|
+
// the latter resolves via the current class's typeBindings
|
|
201
|
+
// (method return-type). We accept both on each hop because class
|
|
202
|
+
// scopes store both method return types and field types under
|
|
203
|
+
// `typeBindings` keyed by the member name.
|
|
204
|
+
const parts = splitChainAtTopLevel(text);
|
|
129
205
|
// Language-specific collection-accessor suffix (C#'s `data.Values`
|
|
130
206
|
// on Dictionary<K,V>, etc.). When the provider hook recognizes
|
|
131
207
|
// the final segment and unwraps the receiver's generic, return
|
|
@@ -133,6 +209,9 @@ export function resolveCompoundReceiverClass(receiverText, inScope, scopes, inde
|
|
|
133
209
|
// because Dictionary-family types aren't local class defs.
|
|
134
210
|
if (options.unwrapCollectionAccessor !== undefined && parts.length >= 2) {
|
|
135
211
|
const last = parts[parts.length - 1];
|
|
212
|
+
const headInner = parts[0];
|
|
213
|
+
if (last === undefined || headInner === undefined)
|
|
214
|
+
return undefined;
|
|
136
215
|
const prefix = parts.slice(0, -1).join('.');
|
|
137
216
|
let prefixType;
|
|
138
217
|
if (parts.length === 2) {
|
|
@@ -143,16 +222,18 @@ export function resolveCompoundReceiverClass(receiverText, inScope, scopes, inde
|
|
|
143
222
|
// to find its typeRef. We need the TypeRef (not the class def)
|
|
144
223
|
// because the hook inspects the raw generic args (e.g.
|
|
145
224
|
// `Dictionary<string, User>`).
|
|
146
|
-
const headInner = parts[0];
|
|
147
225
|
let cur = findReceiverTypeBinding(inScope, headInner, scopes);
|
|
148
226
|
for (let i = 1; i < parts.length - 1 && cur !== undefined; i++) {
|
|
227
|
+
const segment = parts[i];
|
|
228
|
+
if (segment === undefined)
|
|
229
|
+
break;
|
|
149
230
|
const cls = findClassBindingInScope(cur.declaredAtScope, cur.rawName, scopes);
|
|
150
231
|
if (cls === undefined) {
|
|
151
232
|
cur = undefined;
|
|
152
233
|
break;
|
|
153
234
|
}
|
|
154
235
|
const cs = classScopeByDefId.get(cls.nodeId);
|
|
155
|
-
cur = cs?.typeBindings.get(
|
|
236
|
+
cur = cs?.typeBindings.get(segment);
|
|
156
237
|
}
|
|
157
238
|
prefixType = cur;
|
|
158
239
|
}
|
|
@@ -164,20 +245,108 @@ export function resolveCompoundReceiverClass(receiverText, inScope, scopes, inde
|
|
|
164
245
|
}
|
|
165
246
|
}
|
|
166
247
|
const head = parts[0];
|
|
167
|
-
|
|
248
|
+
if (head === undefined)
|
|
249
|
+
return undefined;
|
|
250
|
+
const headMemberName = stripCallParens(head);
|
|
251
|
+
const headType = findReceiverTypeBinding(inScope, headMemberName, scopes);
|
|
168
252
|
let currentClass = headType
|
|
169
253
|
? findClassBindingInScope(headType.declaredAtScope, headType.rawName, scopes)
|
|
170
|
-
:
|
|
254
|
+
: findClassBindingInScope(inScope, headMemberName, scopes);
|
|
255
|
+
// `const user = getUser(); user.address` — the typeBinding for `user`
|
|
256
|
+
// is an alias to the callee name (`getUser`), not a class. When
|
|
257
|
+
// `findClassBinding` on that rawName fails, treat it as a zero-arg
|
|
258
|
+
// call so return-type hoisting resolves to the class (`User`).
|
|
259
|
+
if (currentClass === undefined &&
|
|
260
|
+
headType !== undefined &&
|
|
261
|
+
!headType.rawName.includes('.') &&
|
|
262
|
+
!headType.rawName.includes('(')) {
|
|
263
|
+
currentClass = resolveCompoundReceiverClass(`${headType.rawName}()`, inScope, scopes, index, options, depth + 1);
|
|
264
|
+
}
|
|
171
265
|
for (let i = 1; i < parts.length && currentClass !== undefined; i++) {
|
|
172
|
-
const
|
|
266
|
+
const segment = parts[i];
|
|
267
|
+
if (segment === undefined)
|
|
268
|
+
break;
|
|
269
|
+
const memberName = stripCallParens(segment);
|
|
173
270
|
const cs = classScopeByDefId.get(currentClass.nodeId);
|
|
174
|
-
|
|
175
|
-
if (
|
|
271
|
+
let memberType = cs?.typeBindings.get(memberName);
|
|
272
|
+
if (memberType === undefined &&
|
|
273
|
+
options.hoistTypeBindingsToModule === true &&
|
|
274
|
+
cs !== undefined) {
|
|
275
|
+
let curId = cs.parent;
|
|
276
|
+
while (curId !== null) {
|
|
277
|
+
const curScope = scopes.scopeTree.getScope(curId);
|
|
278
|
+
if (curScope === undefined)
|
|
279
|
+
break;
|
|
280
|
+
const cand = curScope.typeBindings.get(memberName);
|
|
281
|
+
if (cand !== undefined) {
|
|
282
|
+
memberType = cand;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
curId = curScope.parent;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (memberType === undefined) {
|
|
289
|
+
// Trailing segment may be a method name without `()` — e.g.
|
|
290
|
+
// `this.repos.values` from a for-of iterable capture. Try the
|
|
291
|
+
// call-shaped resolver before giving up.
|
|
292
|
+
if (!segment.includes('(')) {
|
|
293
|
+
const prefix = parts.slice(0, i).join('.');
|
|
294
|
+
const asCall = resolveCompoundReceiverClass(`${prefix}.${memberName}()`, inScope, scopes, index, options, depth + 1);
|
|
295
|
+
if (asCall !== undefined)
|
|
296
|
+
return asCall;
|
|
297
|
+
}
|
|
176
298
|
return undefined;
|
|
177
|
-
|
|
299
|
+
}
|
|
300
|
+
let nextClass = findClassBindingInScope(memberType.declaredAtScope, memberType.rawName, scopes);
|
|
301
|
+
if (nextClass === undefined) {
|
|
302
|
+
const fromMap = unwrapMapValueToClass(memberType, scopes);
|
|
303
|
+
if (fromMap !== undefined)
|
|
304
|
+
nextClass = fromMap;
|
|
305
|
+
}
|
|
306
|
+
currentClass = nextClass;
|
|
178
307
|
}
|
|
179
308
|
return currentClass;
|
|
180
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Split a chain expression like `a.b().c.d()` at top-level `.`
|
|
312
|
+
* separators — i.e. `.` characters NOT nested inside balanced
|
|
313
|
+
* `(...)`, `[...]`, or `<...>` delimiters. Returns the segments in
|
|
314
|
+
* order: `['a', 'b()', 'c', 'd()']`. Malformed input falls back to
|
|
315
|
+
* a plain `split('.')`.
|
|
316
|
+
*/
|
|
317
|
+
function splitChainAtTopLevel(text) {
|
|
318
|
+
const out = [];
|
|
319
|
+
let depth = 0;
|
|
320
|
+
let last = 0;
|
|
321
|
+
for (let i = 0; i < text.length; i++) {
|
|
322
|
+
const ch = text[i];
|
|
323
|
+
if (ch === '(' || ch === '[' || ch === '<')
|
|
324
|
+
depth++;
|
|
325
|
+
else if (ch === ')' || ch === ']' || ch === '>')
|
|
326
|
+
depth = Math.max(0, depth - 1);
|
|
327
|
+
else if (ch === '.' && depth === 0) {
|
|
328
|
+
out.push(text.slice(last, i));
|
|
329
|
+
last = i + 1;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
out.push(text.slice(last));
|
|
333
|
+
// Guard against pathological input (`a.` / `.a`) — drop empties.
|
|
334
|
+
return out.filter((s) => s.length > 0);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Strip a trailing `(...)` from a chain segment so typeBinding lookup
|
|
338
|
+
* uses the member name: `'getUser()'` → `'getUser'`. Leaves bare
|
|
339
|
+
* identifiers (`'address'`) unchanged. Arguments inside the parens
|
|
340
|
+
* are discarded — the compound resolver is return-type only.
|
|
341
|
+
*/
|
|
342
|
+
function stripCallParens(segment) {
|
|
343
|
+
if (!segment.endsWith(')'))
|
|
344
|
+
return segment;
|
|
345
|
+
const open = segment.indexOf('(');
|
|
346
|
+
if (open === -1)
|
|
347
|
+
return segment;
|
|
348
|
+
return segment.slice(0, open);
|
|
349
|
+
}
|
|
181
350
|
/** Find the index of the `(` that matches the trailing `)` of a
|
|
182
351
|
* call-expression text. Returns -1 if unbalanced. */
|
|
183
352
|
function matchingOpenParen(text) {
|
|
@@ -196,3 +365,103 @@ function matchingOpenParen(text) {
|
|
|
196
365
|
}
|
|
197
366
|
return -1;
|
|
198
367
|
}
|
|
368
|
+
/** Type arguments of a shallow `Map<K,V>` / `ReadonlyMap<K,V>` (depth-aware). */
|
|
369
|
+
function extractShallowMapTypeArgByIndex(mapText, wantIndex) {
|
|
370
|
+
const t = mapText.trim();
|
|
371
|
+
const m = /^(?:ReadonlyMap|Map)\s*</.exec(t);
|
|
372
|
+
if (m === null || m.index !== 0)
|
|
373
|
+
return undefined;
|
|
374
|
+
const openIdx = m[0].length - 1;
|
|
375
|
+
if (t[openIdx] !== '<')
|
|
376
|
+
return undefined;
|
|
377
|
+
let depth = 1;
|
|
378
|
+
const args = [];
|
|
379
|
+
let segStart = openIdx + 1;
|
|
380
|
+
for (let i = openIdx + 1; i < t.length; i++) {
|
|
381
|
+
const ch = t[i];
|
|
382
|
+
if (ch === '<')
|
|
383
|
+
depth++;
|
|
384
|
+
else if (ch === '>') {
|
|
385
|
+
depth--;
|
|
386
|
+
if (depth === 0) {
|
|
387
|
+
const tail = t.slice(segStart, i).trim();
|
|
388
|
+
if (tail.length > 0)
|
|
389
|
+
args.push(tail);
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else if (ch === ',' && depth === 1) {
|
|
394
|
+
args.push(t.slice(segStart, i).trim());
|
|
395
|
+
segStart = i + 1;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const picked = args[wantIndex]?.trim();
|
|
399
|
+
return picked !== undefined && picked.length > 0 ? picked : undefined;
|
|
400
|
+
}
|
|
401
|
+
function unwrapMapValueToClass(memberType, scopes) {
|
|
402
|
+
const v = extractShallowMapTypeArgByIndex(memberType.rawName, 1);
|
|
403
|
+
if (v === undefined)
|
|
404
|
+
return undefined;
|
|
405
|
+
return findClassBindingInScope(memberType.declaredAtScope, v, scopes);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Walk `objExpr` as a field chain (`this.repos`) and return the `V`
|
|
409
|
+
* type name from a terminal `Map<K,V>` field binding — used when
|
|
410
|
+
* resolving `.values()` without a parsed stdlib return type.
|
|
411
|
+
*/
|
|
412
|
+
function resolveMapValueTypeNameFromPrefix(objExpr, inScope, scopes, index, options) {
|
|
413
|
+
const classScopeByDefId = index.classScopeByDefId;
|
|
414
|
+
const parts = splitChainAtTopLevel(objExpr);
|
|
415
|
+
const head = parts[0];
|
|
416
|
+
if (head === undefined)
|
|
417
|
+
return undefined;
|
|
418
|
+
const headMemberName = stripCallParens(head);
|
|
419
|
+
const headType = findReceiverTypeBinding(inScope, headMemberName, scopes);
|
|
420
|
+
let currentClass = headType
|
|
421
|
+
? findClassBindingInScope(headType.declaredAtScope, headType.rawName, scopes)
|
|
422
|
+
: findClassBindingInScope(inScope, headMemberName, scopes);
|
|
423
|
+
if (currentClass === undefined &&
|
|
424
|
+
headType !== undefined &&
|
|
425
|
+
!headType.rawName.includes('.') &&
|
|
426
|
+
!headType.rawName.includes('(')) {
|
|
427
|
+
currentClass = resolveCompoundReceiverClass(`${headType.rawName}()`, inScope, scopes, index, options, 1);
|
|
428
|
+
}
|
|
429
|
+
let lastMemberType;
|
|
430
|
+
for (let i = 1; i < parts.length && currentClass !== undefined; i++) {
|
|
431
|
+
const segment = parts[i];
|
|
432
|
+
if (segment === undefined)
|
|
433
|
+
break;
|
|
434
|
+
const memberName = stripCallParens(segment);
|
|
435
|
+
const cs = classScopeByDefId.get(currentClass.nodeId);
|
|
436
|
+
if (cs === undefined)
|
|
437
|
+
return undefined;
|
|
438
|
+
let memberType = cs.typeBindings.get(memberName);
|
|
439
|
+
if (memberType === undefined && options.hoistTypeBindingsToModule === true) {
|
|
440
|
+
let curId = cs.parent;
|
|
441
|
+
while (curId !== null) {
|
|
442
|
+
const curScope = scopes.scopeTree.getScope(curId);
|
|
443
|
+
if (curScope === undefined)
|
|
444
|
+
break;
|
|
445
|
+
const cand = curScope.typeBindings.get(memberName);
|
|
446
|
+
if (cand !== undefined) {
|
|
447
|
+
memberType = cand;
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
curId = curScope.parent;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (memberType === undefined)
|
|
454
|
+
return undefined;
|
|
455
|
+
lastMemberType = memberType;
|
|
456
|
+
let nextClass = findClassBindingInScope(memberType.declaredAtScope, memberType.rawName, scopes);
|
|
457
|
+
if (nextClass === undefined) {
|
|
458
|
+
const fromMap = unwrapMapValueToClass(memberType, scopes);
|
|
459
|
+
if (fromMap !== undefined)
|
|
460
|
+
nextClass = fromMap;
|
|
461
|
+
}
|
|
462
|
+
currentClass = nextClass;
|
|
463
|
+
}
|
|
464
|
+
if (lastMemberType === undefined)
|
|
465
|
+
return undefined;
|
|
466
|
+
return extractShallowMapTypeArgByIndex(lastMemberType.rawName, 1);
|
|
467
|
+
}
|
|
@@ -14,8 +14,29 @@
|
|
|
14
14
|
* populated) but BEFORE `resolveReferenceSites` (so resolution
|
|
15
15
|
* sees the propagated types).
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* **Ordering invariant (added 2026-04-24, RFC #909 Ring 3 / PR #1050):**
|
|
18
|
+
* The pass walks files in `indexes.sccs` reverse-topological order
|
|
19
|
+
* (leaves first per `tarjanSccs`). For each importer we chain-follow
|
|
20
|
+
* the source module's typeBindings BEFORE mirroring, so a multi-hop
|
|
21
|
+
* alias chain like
|
|
22
|
+
*
|
|
23
|
+
* models.ts: function getUser(): User
|
|
24
|
+
* service.ts: export const user = getUser() // user → getUser
|
|
25
|
+
* app.ts: import { user } from './service' // user → ?
|
|
26
|
+
*
|
|
27
|
+
* collapses to `app.user → User` in a single pass instead of stopping
|
|
28
|
+
* at the intermediate `getUser` ref. The motivating regression is the
|
|
29
|
+
* `ts-simple` integration fixture (`gitnexus/test/fixtures/scope-
|
|
30
|
+
* resolution/cross-file-binding/ts-simple/`), where `user.save()` and
|
|
31
|
+
* `user.getName()` only resolve when the chain collapse happens
|
|
32
|
+
* topologically.
|
|
33
|
+
*
|
|
34
|
+
* Cyclic SCCs reach a partial fixpoint via the same mirror step but
|
|
35
|
+
* are not guaranteed to fully resolve — see the `ts-circular`
|
|
36
|
+
* fixture, which only asserts pipeline-no-throw.
|
|
37
|
+
*
|
|
38
|
+
* Generic; promoted from `languages/python/scope-resolver.ts` per the
|
|
39
|
+
* scope-resolution generalization plan.
|
|
19
40
|
*/
|
|
20
41
|
import type { ParsedFile } from '../../../../_shared/index.js';
|
|
21
42
|
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
@@ -14,10 +14,39 @@
|
|
|
14
14
|
* populated) but BEFORE `resolveReferenceSites` (so resolution
|
|
15
15
|
* sees the propagated types).
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* **Ordering invariant (added 2026-04-24, RFC #909 Ring 3 / PR #1050):**
|
|
18
|
+
* The pass walks files in `indexes.sccs` reverse-topological order
|
|
19
|
+
* (leaves first per `tarjanSccs`). For each importer we chain-follow
|
|
20
|
+
* the source module's typeBindings BEFORE mirroring, so a multi-hop
|
|
21
|
+
* alias chain like
|
|
22
|
+
*
|
|
23
|
+
* models.ts: function getUser(): User
|
|
24
|
+
* service.ts: export const user = getUser() // user → getUser
|
|
25
|
+
* app.ts: import { user } from './service' // user → ?
|
|
26
|
+
*
|
|
27
|
+
* collapses to `app.user → User` in a single pass instead of stopping
|
|
28
|
+
* at the intermediate `getUser` ref. The motivating regression is the
|
|
29
|
+
* `ts-simple` integration fixture (`gitnexus/test/fixtures/scope-
|
|
30
|
+
* resolution/cross-file-binding/ts-simple/`), where `user.save()` and
|
|
31
|
+
* `user.getName()` only resolve when the chain collapse happens
|
|
32
|
+
* topologically.
|
|
33
|
+
*
|
|
34
|
+
* Cyclic SCCs reach a partial fixpoint via the same mirror step but
|
|
35
|
+
* are not guaranteed to fully resolve — see the `ts-circular`
|
|
36
|
+
* fixture, which only asserts pipeline-no-throw.
|
|
37
|
+
*
|
|
38
|
+
* Generic; promoted from `languages/python/scope-resolver.ts` per the
|
|
39
|
+
* scope-resolution generalization plan.
|
|
40
|
+
*/
|
|
41
|
+
import { lookupBindingsAt, namesAtScope } from '../scope/walkers.js';
|
|
42
|
+
/**
|
|
43
|
+
* Max chain depth for the post-finalize re-follow. Effective end-to-end
|
|
44
|
+
* depth is roughly 2× this number, because chain-following runs once
|
|
45
|
+
* inside each importer's source module before mirroring AND once on
|
|
46
|
+
* the importer's own typeBindings after mirroring; deeply nested
|
|
47
|
+
* intra-module aliases can compose with cross-file aliases of the same
|
|
48
|
+
* depth. 8 covers all production fixtures with headroom.
|
|
19
49
|
*/
|
|
20
|
-
/** Max chain depth for the post-finalize re-follow. */
|
|
21
50
|
const RECHAIN_MAX_DEPTH = 8;
|
|
22
51
|
/** Walk `ref.rawName` through the scope chain's typeBindings looking
|
|
23
52
|
* for a terminal class-like rawName. Mirrors the in-extractor
|
|
@@ -78,47 +107,90 @@ function followChainPostFinalize(start, fromScopeId, scopes) {
|
|
|
78
107
|
*/
|
|
79
108
|
export function propagateImportedReturnTypes(parsedFiles, indexes, index) {
|
|
80
109
|
const moduleScopeByFile = index.moduleScopeByFile;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
110
|
+
// Walk SCCs in reverse-topological order (`indexes.sccs` is leaves-
|
|
111
|
+
// first per `tarjanSccs`). For each file we mirror import bindings
|
|
112
|
+
// AFTER chain-following the source module's typeBindings, so a
|
|
113
|
+
// multi-hop alias chain like
|
|
114
|
+
// models.ts: function getUser(): User
|
|
115
|
+
// service.ts: export const user = getUser() // user → getUser
|
|
116
|
+
// app.ts: import { user } from './service' // user → ?
|
|
117
|
+
// collapses to `app.user → User` instead of stopping at the
|
|
118
|
+
// intermediate `getUser` ref. Without topological ordering, app.ts
|
|
119
|
+
// could be processed before service.ts had its own typeBindings
|
|
120
|
+
// chain-followed, leaving the importer with an unresolvable interim
|
|
121
|
+
// ref. Cyclic SCCs reach a partial fixpoint via the same mirror
|
|
122
|
+
// step but are not guaranteed to fully resolve — see the
|
|
123
|
+
// ts-circular cross-file-binding fixture which only asserts that
|
|
124
|
+
// the pipeline does not throw.
|
|
125
|
+
for (const scc of indexes.sccs) {
|
|
126
|
+
for (const filePath of scc.files) {
|
|
127
|
+
const importerModule = moduleScopeByFile.get(filePath);
|
|
128
|
+
if (importerModule === undefined)
|
|
92
129
|
continue;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
130
|
+
// Iterate finalized + augmented binding names at this scope so
|
|
131
|
+
// post-finalize hooks (e.g. `using static` augmentations from
|
|
132
|
+
// `populateCsharpNamespaceSiblings`) are visible to the
|
|
133
|
+
// import-derived typeBinding mirror. Both helpers fast-path when
|
|
134
|
+
// no augmentations exist for the scope, so the common case is
|
|
135
|
+
// allocation-free. See I8.
|
|
136
|
+
for (const localName of namesAtScope(importerModule.id, indexes)) {
|
|
137
|
+
// Skip if importer already has a typeBinding for this name —
|
|
138
|
+
// an explicit local annotation must win over import-derived.
|
|
139
|
+
if (importerModule.typeBindings.has(localName))
|
|
98
140
|
continue;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
141
|
+
const refs = lookupBindingsAt(importerModule.id, localName, indexes);
|
|
142
|
+
for (const ref of refs) {
|
|
143
|
+
if (ref.origin !== 'import' && ref.origin !== 'reexport')
|
|
144
|
+
continue;
|
|
145
|
+
const sourceModule = moduleScopeByFile.get(ref.def.filePath);
|
|
146
|
+
if (sourceModule === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
// The source file's typeBinding is keyed by the def's simple
|
|
149
|
+
// name (e.g. `get_user`), not the importer's local alias.
|
|
150
|
+
const qn = ref.def.qualifiedName;
|
|
151
|
+
if (qn === undefined)
|
|
152
|
+
continue;
|
|
153
|
+
const dot = qn.lastIndexOf('.');
|
|
154
|
+
const sourceName = dot === -1 ? qn : qn.slice(dot + 1);
|
|
155
|
+
const sourceTypeRef = sourceModule.typeBindings.get(sourceName);
|
|
156
|
+
if (sourceTypeRef === undefined)
|
|
157
|
+
continue;
|
|
158
|
+
// Chain-follow inside the source module so we mirror the
|
|
159
|
+
// terminal type, not an intermediate intra-source reference.
|
|
160
|
+
const terminal = followChainPostFinalize(sourceTypeRef, sourceModule.id, indexes);
|
|
161
|
+
// Mutating typeBindings is safe because draftToScope
|
|
162
|
+
// produced a non-frozen Map (Contract Invariant I3/I8).
|
|
163
|
+
importerModule.typeBindings.set(localName, terminal);
|
|
164
|
+
// First-write-wins for the local alias: if the same
|
|
165
|
+
// `localName` was registered multiple times via
|
|
166
|
+
// `mergeBindings` (rare; happens with conflicting
|
|
167
|
+
// re-exports), only the first ref with a usable
|
|
168
|
+
// typeBinding source is mirrored. Conflict resolution
|
|
169
|
+
// among multiple sources is the merger's job, not ours.
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Chain-follow this importer's own module typeBindings now —
|
|
174
|
+
// any local `const x = importedFn()` resolves while we have
|
|
175
|
+
// freshly-mirrored bindings, and downstream importers in a
|
|
176
|
+
// later (closer-to-root) SCC will see x's terminal type rather
|
|
177
|
+
// than an intra-module call ref.
|
|
178
|
+
for (const [name, ref] of importerModule.typeBindings) {
|
|
179
|
+
const resolved = followChainPostFinalize(ref, importerModule.id, indexes);
|
|
180
|
+
if (resolved !== ref) {
|
|
181
|
+
importerModule.typeBindings.set(name, resolved);
|
|
182
|
+
}
|
|
115
183
|
}
|
|
116
184
|
}
|
|
117
185
|
}
|
|
118
|
-
//
|
|
119
|
-
//
|
|
186
|
+
// Final pass: chain-follow non-module scopes (function-local
|
|
187
|
+
// typeBindings). Module scopes were already followed inside the
|
|
188
|
+
// SCC loop above.
|
|
120
189
|
for (const parsed of parsedFiles) {
|
|
190
|
+
const moduleScopeId = moduleScopeByFile.get(parsed.filePath)?.id;
|
|
121
191
|
for (const scope of parsed.scopes) {
|
|
192
|
+
if (scope.id === moduleScopeId)
|
|
193
|
+
continue;
|
|
122
194
|
for (const [name, ref] of scope.typeBindings) {
|
|
123
195
|
const resolved = followChainPostFinalize(ref, scope.id, indexes);
|
|
124
196
|
if (resolved !== ref) {
|
|
@@ -86,8 +86,10 @@ export const defaultLinearize = (_classDefId, directParents, parentsByDefId) =>
|
|
|
86
86
|
const ancestors = [];
|
|
87
87
|
const visited = new Set();
|
|
88
88
|
const queue = [...directParents];
|
|
89
|
-
|
|
89
|
+
for (;;) {
|
|
90
90
|
const cur = queue.shift();
|
|
91
|
+
if (cur === undefined)
|
|
92
|
+
break;
|
|
91
93
|
if (visited.has(cur))
|
|
92
94
|
continue;
|
|
93
95
|
visited.add(cur);
|
|
@@ -234,10 +234,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
// ── Case 3b: chain-typebinding (`city → user.get_city`) ──────
|
|
237
|
-
|
|
238
|
-
typeRef.rawName.
|
|
239
|
-
|
|
240
|
-
|
|
237
|
+
const chainHead = typeRef !== undefined && typeRef.rawName.includes('.') && !typeRef.rawName.includes('(')
|
|
238
|
+
? (typeRef.rawName.split('.', 1)[0] ?? '')
|
|
239
|
+
: undefined;
|
|
240
|
+
if (typeRef !== undefined && chainHead !== undefined && !namespaceTargets.has(chainHead)) {
|
|
241
241
|
// Try the plain dotted-field walk first — covers property /
|
|
242
242
|
// collection-accessor shapes (`.Values`, Kotlin `.size`) and
|
|
243
243
|
// field chains. Fall back to call-form (`x()`) which treats
|
|
@@ -269,7 +269,15 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
269
269
|
}
|
|
270
270
|
// ── Case 4: simple typeBinding (`u: U`) ──────────────────────
|
|
271
271
|
if (typeRef !== undefined && !typeRef.rawName.includes('.')) {
|
|
272
|
-
|
|
272
|
+
let ownerDef = findClassBindingInScope(site.inScope, typeRef.rawName, scopes);
|
|
273
|
+
// `findClassBindingInScope(..., typeRef.rawName)` only works when
|
|
274
|
+
// rawName is itself a class symbol. Map for-of tuple bindings
|
|
275
|
+
// (`__MAP_TUPLE_i__:mapId`), callable aliases (`getUser` → User),
|
|
276
|
+
// and other compound-friendly shapes need the compound resolver
|
|
277
|
+
// keyed by the receiver identifier.
|
|
278
|
+
if (ownerDef === undefined) {
|
|
279
|
+
ownerDef = resolveCompoundReceiverClass(receiverName, site.inScope, scopes, index, compoundOpts);
|
|
280
|
+
}
|
|
273
281
|
if (ownerDef !== undefined) {
|
|
274
282
|
const chain = [ownerDef.nodeId, ...scopes.methodDispatch.mroFor(ownerDef.nodeId)];
|
|
275
283
|
let memberDef;
|
|
@@ -32,7 +32,7 @@ import { getLanguageFromFilename } from '../../../../_shared/index.js';
|
|
|
32
32
|
import { readFileContents } from '../../filesystem-walker.js';
|
|
33
33
|
import { runScopeResolution } from './run.js';
|
|
34
34
|
import { SCOPE_RESOLVERS } from './registry.js';
|
|
35
|
-
import { isDev } from '../../utils/env.js';
|
|
35
|
+
import { isDev, isSemanticModelValidatorEnabled } from '../../utils/env.js';
|
|
36
36
|
const NOOP_OUTPUT = Object.freeze({
|
|
37
37
|
ran: false,
|
|
38
38
|
filesProcessed: 0,
|
|
@@ -87,14 +87,23 @@ export const scopeResolutionPhase = {
|
|
|
87
87
|
if (content !== undefined)
|
|
88
88
|
files.push({ path: fp, content });
|
|
89
89
|
}
|
|
90
|
+
// Load per-language import-resolution config (tsconfig paths,
|
|
91
|
+
// composer.json autoload, go.mod, ...). One I/O round trip per
|
|
92
|
+
// workspace pass — cached implicitly by the result handed to
|
|
93
|
+
// every `resolveImportTarget` call below.
|
|
94
|
+
const resolutionConfig = provider.loadResolutionConfig !== undefined
|
|
95
|
+
? await provider.loadResolutionConfig(ctx.repoPath)
|
|
96
|
+
: undefined;
|
|
90
97
|
const stats = runScopeResolution({
|
|
91
98
|
graph: ctx.graph,
|
|
92
99
|
model,
|
|
93
100
|
files,
|
|
94
101
|
treeCache: scopeTreeCache,
|
|
102
|
+
resolutionConfig,
|
|
95
103
|
onWarn: (msg) => {
|
|
96
|
-
if (
|
|
104
|
+
if (isSemanticModelValidatorEnabled()) {
|
|
97
105
|
console.warn(`[scope-resolution:${lang}] ${msg}`);
|
|
106
|
+
}
|
|
98
107
|
},
|
|
99
108
|
}, provider);
|
|
100
109
|
anyRan = true;
|