gitnexus 1.6.8-rc.2 → 1.6.8-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 +20 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +4 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/ai-context.js +1 -0
- package/dist/cli/analyze-config.js +40 -0
- package/dist/cli/analyze.d.ts +22 -0
- package/dist/cli/analyze.js +36 -6
- package/dist/cli/clean.d.ts +1 -0
- package/dist/cli/clean.js +43 -1
- package/dist/cli/eval-server.js +43 -0
- package/dist/cli/help-i18n.js +11 -0
- package/dist/cli/i18n/en.d.ts +13 -0
- package/dist/cli/i18n/en.js +13 -0
- package/dist/cli/i18n/resources.d.ts +26 -0
- package/dist/cli/i18n/zh-CN.d.ts +13 -0
- package/dist/cli/i18n/zh-CN.js +13 -0
- package/dist/cli/index.js +19 -0
- package/dist/cli/list.js +12 -0
- package/dist/cli/optional-grammars.d.ts +6 -8
- package/dist/cli/optional-grammars.js +8 -11
- package/dist/cli/skill-gen.js +1 -0
- package/dist/cli/status.js +26 -5
- package/dist/cli/tool.d.ts +12 -1
- package/dist/cli/tool.js +41 -1
- package/dist/core/embeddings/embedder.js +4 -0
- package/dist/core/embeddings/embedding-pipeline.js +27 -16
- package/dist/core/embeddings/onnxruntime-common-resolver.d.ts +6 -0
- package/dist/core/embeddings/onnxruntime-common-resolver.js +130 -0
- package/dist/core/graph/import-cycles.d.ts +10 -0
- package/dist/core/graph/import-cycles.js +103 -0
- package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
- package/dist/core/group/extractors/http-patterns/java.js +3 -50
- package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
- package/dist/core/group/extractors/include-extractor.js +7 -7
- package/dist/core/ingestion/cfg/cfg-builder.d.ts +64 -0
- package/dist/core/ingestion/cfg/cfg-builder.js +129 -0
- package/dist/core/ingestion/cfg/collect.d.ts +30 -0
- package/dist/core/ingestion/cfg/collect.js +34 -0
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +97 -0
- package/dist/core/ingestion/cfg/control-flow-context.js +113 -0
- package/dist/core/ingestion/cfg/emit.d.ts +144 -0
- package/dist/core/ingestion/cfg/emit.js +315 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +90 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +364 -0
- package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
- package/dist/core/ingestion/cfg/traversal-result.js +2 -0
- package/dist/core/ingestion/cfg/types.d.ts +231 -0
- package/dist/core/ingestion/cfg/types.js +13 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +144 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1032 -0
- package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
- package/dist/core/ingestion/cfg/visitors/typescript.js +584 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -2
- package/dist/core/ingestion/languages/c-cpp.js +11 -4
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +6 -2
- package/dist/core/ingestion/languages/cpp/captures.js +24 -1
- package/dist/core/ingestion/languages/cpp/query.js +23 -0
- package/dist/core/ingestion/languages/java.js +3 -0
- package/dist/core/ingestion/languages/kotlin/query.js +3 -2
- package/dist/core/ingestion/languages/kotlin.js +5 -1
- package/dist/core/ingestion/languages/typescript.js +5 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
- package/dist/core/ingestion/method-extractors/generic.js +1 -0
- package/dist/core/ingestion/method-types.d.ts +2 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
- package/dist/core/ingestion/model/symbol-table.js +1 -0
- package/dist/core/ingestion/parsing-processor.js +22 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
- package/dist/core/ingestion/pipeline-phases/routes.js +64 -14
- package/dist/core/ingestion/pipeline.d.ts +57 -0
- package/dist/core/ingestion/route-extractors/spring-shared.d.ts +50 -0
- package/dist/core/ingestion/route-extractors/spring-shared.js +80 -0
- package/dist/core/ingestion/route-extractors/spring.d.ts +35 -0
- package/dist/core/ingestion/route-extractors/spring.js +136 -0
- package/dist/core/ingestion/scope-extractor.js +3 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +12 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +145 -24
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +6 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +207 -0
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/taint/emit.d.ts +124 -0
- package/dist/core/ingestion/taint/emit.js +204 -0
- package/dist/core/ingestion/taint/match.d.ts +153 -0
- package/dist/core/ingestion/taint/match.js +278 -0
- package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
- package/dist/core/ingestion/taint/path-codec.js +190 -0
- package/dist/core/ingestion/taint/propagate.d.ts +216 -0
- package/dist/core/ingestion/taint/propagate.js +664 -0
- package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
- package/dist/core/ingestion/taint/site-safety.js +98 -0
- package/dist/core/ingestion/taint/source-sink-config.d.ts +94 -23
- package/dist/core/ingestion/taint/source-sink-config.js +11 -11
- package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
- package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
- package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
- package/dist/core/ingestion/taint/typescript-model.js +102 -0
- package/dist/core/ingestion/utils/method-props.js +1 -0
- package/dist/core/ingestion/workers/clone-safety.d.ts +109 -0
- package/dist/core/ingestion/workers/clone-safety.js +465 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +11 -0
- package/dist/core/ingestion/workers/parse-worker.js +70 -53
- package/dist/core/ingestion/workers/post-result.d.ts +22 -0
- package/dist/core/ingestion/workers/post-result.js +87 -0
- package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
- package/dist/core/ingestion/workers/result-merge.js +43 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
- package/dist/core/ingestion/workers/worker-pool.js +33 -16
- package/dist/core/lbug/lbug-adapter.d.ts +19 -0
- package/dist/core/lbug/lbug-adapter.js +56 -1
- package/dist/core/run-analyze.d.ts +87 -0
- package/dist/core/run-analyze.js +280 -25
- package/dist/core/tree-sitter/parser-loader.js +5 -4
- package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
- package/dist/core/tree-sitter/vendored-grammars.js +57 -0
- package/dist/mcp/core/embedder.js +4 -0
- package/dist/mcp/local/local-backend.d.ts +86 -2
- package/dist/mcp/local/local-backend.js +657 -27
- package/dist/mcp/resources.js +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.js +109 -0
- package/dist/server/analyze-launch.d.ts +29 -0
- package/dist/server/analyze-launch.js +137 -0
- package/dist/server/analyze-upload.d.ts +33 -0
- package/dist/server/analyze-upload.js +123 -0
- package/dist/server/analyze-worker-ipc.d.ts +58 -0
- package/dist/server/analyze-worker-ipc.js +16 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/analyze-worker.js +7 -1
- package/dist/server/api.js +46 -140
- package/dist/server/git-clone.d.ts +1 -0
- package/dist/server/git-clone.js +1 -1
- package/dist/server/middleware.d.ts +11 -0
- package/dist/server/middleware.js +27 -0
- package/dist/server/upload-ingest.d.ts +56 -0
- package/dist/server/upload-ingest.js +276 -0
- package/dist/server/upload-paths.d.ts +31 -0
- package/dist/server/upload-paths.js +51 -0
- package/dist/server/upload-sweep.d.ts +20 -0
- package/dist/server/upload-sweep.js +57 -0
- package/dist/storage/branch-index.d.ts +52 -0
- package/dist/storage/branch-index.js +65 -0
- package/dist/storage/git.d.ts +11 -0
- package/dist/storage/git.js +28 -0
- package/dist/storage/parse-cache.d.ts +22 -1
- package/dist/storage/parse-cache.js +32 -10
- package/dist/storage/repo-manager.d.ts +122 -10
- package/dist/storage/repo-manager.js +162 -21
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +42 -8
- package/hooks/claude/gitnexus-hook.cjs +36 -8
- package/hooks/claude/hook-db-lock-probe.cjs +123 -2
- package/package.json +4 -2
- package/scripts/assert-publish-grammar-coverage.cjs +31 -0
- package/scripts/build-tree-sitter-grammars.cjs +16 -10
- package/skills/gitnexus-guide.md +11 -0
- package/vendor/tree-sitter-c/package.json +1 -1
- package/vendor/tree-sitter-dart/package.json +1 -1
- package/vendor/tree-sitter-kotlin/package.json +1 -1
- package/vendor/tree-sitter-proto/package.json +1 -1
- package/vendor/tree-sitter-swift/package.json +1 -1
- package/web/assets/{agent-CKPMqImC.js → agent-ay4LD70X.js} +1 -1
- package/web/assets/{architectureDiagram-UL44E2DR-D1EXI0zA.js → architectureDiagram-UL44E2DR-Dc-viYhd.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-4rpojOyj.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-BnOy944n.js} +1 -1
- package/web/assets/{classDiagram-KGZ6W3CR-Bv93af_b.js → classDiagram-KGZ6W3CR-Bsgpy98Q.js} +1 -1
- package/web/assets/{classDiagram-v2-72OJOZXJ-CLfEqHUa.js → classDiagram-v2-72OJOZXJ-D5atDGjc.js} +1 -1
- package/web/assets/{diagram-3NCE3AQN-BqAtKUpW.js → diagram-3NCE3AQN-Cz1OEMVi.js} +1 -1
- package/web/assets/{diagram-GF46GFSD-CaBG6n6o.js → diagram-GF46GFSD-CWYwzfP8.js} +1 -1
- package/web/assets/{diagram-QXG6HAR7-CZ-O3rcV.js → diagram-QXG6HAR7-DtBObr8L.js} +1 -1
- package/web/assets/{diagram-WEQXMOUZ-P4lSL4GH.js → diagram-WEQXMOUZ-BUV44Ov_.js} +1 -1
- package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-Ds9s-sRF.js} +1 -1
- package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-CupStHQb.js} +1 -1
- package/web/assets/index-COMMmbxW.css +2 -0
- package/web/assets/{index-CG6q8eTs.js → index-Do8AE5yF.js} +85 -85
- package/web/assets/{infoDiagram-3YFTVSEB-DmLICZx1.js → infoDiagram-3YFTVSEB-DPTXSbgu.js} +1 -1
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CeK-Qhet.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-D4tx6QQ-.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-Bcto_kGa.js} +1 -1
- package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-DPIeBTG6.js} +1 -1
- package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-DDGXjVKO.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-BzDqpcVW.js} +1 -1
- package/web/assets/{stateDiagram-7D4R322I-CB2nABwH.js → stateDiagram-7D4R322I-Bx__als3.js} +1 -1
- package/web/assets/{stateDiagram-v2-36443NZ5-COBGd2RL.js → stateDiagram-v2-36443NZ5-QqOh2yO-.js} +1 -1
- package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-C1eogTOG.js} +1 -1
- package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-D20F4rSW.js} +1 -1
- package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-BeBHg7ST.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-CdOWeoNI.js} +1 -1
- package/web/index.html +2 -2
- package/scripts/materialize-vendor-grammars.cjs +0 -97
- package/web/assets/index-BKWA-m7o.css +0 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Spring route-annotation primitives.
|
|
3
|
+
*
|
|
4
|
+
* These are the low-level building blocks the two Spring route extractors —
|
|
5
|
+
* the ingestion-layer `route-extractors/spring.ts` (produces graph `Route`
|
|
6
|
+
* nodes) and the group-layer `group/extractors/http-patterns/java.ts`
|
|
7
|
+
* (produces cross-repo HTTP contracts) — would otherwise each maintain
|
|
8
|
+
* independently. Centralising the annotation→method map, the enclosing-class
|
|
9
|
+
* lookup, and the route-key filter keeps those semantics in one place so the
|
|
10
|
+
* two extractors can't drift apart.
|
|
11
|
+
*
|
|
12
|
+
* This module lives in `ingestion/` (the lower layer); the group layer imports
|
|
13
|
+
* from it, matching the existing `group → ingestion` dependency direction
|
|
14
|
+
* (e.g. `group/extractors/include-extractor.ts` already imports
|
|
15
|
+
* `ingestion/import-resolvers/utils.ts`). It MUST NOT import anything from
|
|
16
|
+
* `group/` to avoid a dependency cycle.
|
|
17
|
+
*/
|
|
18
|
+
import type Parser from 'tree-sitter';
|
|
19
|
+
/**
|
|
20
|
+
* Spring shortcut method-annotation → HTTP verb.
|
|
21
|
+
*
|
|
22
|
+
* `@RequestMapping` is intentionally absent: on a method it carries no implicit
|
|
23
|
+
* verb (the verb lives in its `method = RequestMethod.X` attribute), and on a
|
|
24
|
+
* class it is a URL prefix rather than a route. Callers handle `@RequestMapping`
|
|
25
|
+
* separately.
|
|
26
|
+
*/
|
|
27
|
+
export declare const METHOD_ANNOTATION_TO_HTTP: Record<string, string>;
|
|
28
|
+
/**
|
|
29
|
+
* A named annotation argument contributes a route only when its member key is
|
|
30
|
+
* `path` or `value`; a positional argument (no key node) always qualifies.
|
|
31
|
+
* Drops Spring's non-route string attributes (`produces`, `consumes`,
|
|
32
|
+
* `headers`, `name`, `params`) that would otherwise be mis-read as routes.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isRouteMemberKey(keyNode: Parser.SyntaxNode | undefined): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Find the nearest enclosing `class_declaration` ancestor for a node, or null
|
|
37
|
+
* if the node is top-level. Tree-sitter's `SyntaxNode.parent` walks one level
|
|
38
|
+
* at a time.
|
|
39
|
+
*/
|
|
40
|
+
export declare function findEnclosingClass(node: Parser.SyntaxNode): Parser.SyntaxNode | null;
|
|
41
|
+
/**
|
|
42
|
+
* Strip enclosing quotes from a tree-sitter string-literal node's text.
|
|
43
|
+
* Handles single / double / template (backtick) quotes and triple-quoted
|
|
44
|
+
* strings. Mirrors the safer semantics of the group layer's `unquoteLiteral`:
|
|
45
|
+
* returns `null` for empty / nullish input so callers can uniformly skip
|
|
46
|
+
* captures whose value is missing, and returns the text unchanged when it
|
|
47
|
+
* carries no recognisable surrounding quotes (some grammars expose string
|
|
48
|
+
* content without quotes already).
|
|
49
|
+
*/
|
|
50
|
+
export declare function unquoteSpringLiteral(raw: string): string | null;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Spring route-annotation primitives.
|
|
3
|
+
*
|
|
4
|
+
* These are the low-level building blocks the two Spring route extractors —
|
|
5
|
+
* the ingestion-layer `route-extractors/spring.ts` (produces graph `Route`
|
|
6
|
+
* nodes) and the group-layer `group/extractors/http-patterns/java.ts`
|
|
7
|
+
* (produces cross-repo HTTP contracts) — would otherwise each maintain
|
|
8
|
+
* independently. Centralising the annotation→method map, the enclosing-class
|
|
9
|
+
* lookup, and the route-key filter keeps those semantics in one place so the
|
|
10
|
+
* two extractors can't drift apart.
|
|
11
|
+
*
|
|
12
|
+
* This module lives in `ingestion/` (the lower layer); the group layer imports
|
|
13
|
+
* from it, matching the existing `group → ingestion` dependency direction
|
|
14
|
+
* (e.g. `group/extractors/include-extractor.ts` already imports
|
|
15
|
+
* `ingestion/import-resolvers/utils.ts`). It MUST NOT import anything from
|
|
16
|
+
* `group/` to avoid a dependency cycle.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Spring shortcut method-annotation → HTTP verb.
|
|
20
|
+
*
|
|
21
|
+
* `@RequestMapping` is intentionally absent: on a method it carries no implicit
|
|
22
|
+
* verb (the verb lives in its `method = RequestMethod.X` attribute), and on a
|
|
23
|
+
* class it is a URL prefix rather than a route. Callers handle `@RequestMapping`
|
|
24
|
+
* separately.
|
|
25
|
+
*/
|
|
26
|
+
export const METHOD_ANNOTATION_TO_HTTP = {
|
|
27
|
+
GetMapping: 'GET',
|
|
28
|
+
PostMapping: 'POST',
|
|
29
|
+
PutMapping: 'PUT',
|
|
30
|
+
DeleteMapping: 'DELETE',
|
|
31
|
+
PatchMapping: 'PATCH',
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* A named annotation argument contributes a route only when its member key is
|
|
35
|
+
* `path` or `value`; a positional argument (no key node) always qualifies.
|
|
36
|
+
* Drops Spring's non-route string attributes (`produces`, `consumes`,
|
|
37
|
+
* `headers`, `name`, `params`) that would otherwise be mis-read as routes.
|
|
38
|
+
*/
|
|
39
|
+
export function isRouteMemberKey(keyNode) {
|
|
40
|
+
if (!keyNode)
|
|
41
|
+
return true;
|
|
42
|
+
return keyNode.text === 'path' || keyNode.text === 'value';
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Find the nearest enclosing `class_declaration` ancestor for a node, or null
|
|
46
|
+
* if the node is top-level. Tree-sitter's `SyntaxNode.parent` walks one level
|
|
47
|
+
* at a time.
|
|
48
|
+
*/
|
|
49
|
+
export function findEnclosingClass(node) {
|
|
50
|
+
let cur = node.parent;
|
|
51
|
+
while (cur) {
|
|
52
|
+
if (cur.type === 'class_declaration')
|
|
53
|
+
return cur;
|
|
54
|
+
cur = cur.parent;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Strip enclosing quotes from a tree-sitter string-literal node's text.
|
|
60
|
+
* Handles single / double / template (backtick) quotes and triple-quoted
|
|
61
|
+
* strings. Mirrors the safer semantics of the group layer's `unquoteLiteral`:
|
|
62
|
+
* returns `null` for empty / nullish input so callers can uniformly skip
|
|
63
|
+
* captures whose value is missing, and returns the text unchanged when it
|
|
64
|
+
* carries no recognisable surrounding quotes (some grammars expose string
|
|
65
|
+
* content without quotes already).
|
|
66
|
+
*/
|
|
67
|
+
export function unquoteSpringLiteral(raw) {
|
|
68
|
+
if (!raw)
|
|
69
|
+
return null;
|
|
70
|
+
if ((raw.startsWith('"""') && raw.endsWith('"""')) ||
|
|
71
|
+
(raw.startsWith("'''") && raw.endsWith("'''"))) {
|
|
72
|
+
return raw.slice(3, -3);
|
|
73
|
+
}
|
|
74
|
+
const first = raw[0];
|
|
75
|
+
const last = raw[raw.length - 1];
|
|
76
|
+
if ((first === '"' || first === "'" || first === '`') && last === first && raw.length >= 2) {
|
|
77
|
+
return raw.slice(1, -1);
|
|
78
|
+
}
|
|
79
|
+
return raw;
|
|
80
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spring route annotation extractor for the ingestion pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Extracts `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`,
|
|
5
|
+
* `@PatchMapping`, and `@RequestMapping` annotations from Java source files
|
|
6
|
+
* and returns `ExtractedDecoratorRoute[]` with class-level `@RequestMapping`
|
|
7
|
+
* prefixes already resolved per-class.
|
|
8
|
+
*
|
|
9
|
+
* This module is the ingestion-layer counterpart of
|
|
10
|
+
* `group/extractors/http-patterns/java.ts` (which extracts HTTP contracts
|
|
11
|
+
* for cross-repo matching). It uses the same tree-sitter capture approach:
|
|
12
|
+
* a single predicate-free query matches all route annotations generically,
|
|
13
|
+
* then a for-loop discriminates class-level prefixes from method-level routes
|
|
14
|
+
* by reading `@node.type` and the annotation name.
|
|
15
|
+
*
|
|
16
|
+
* The query is predicate-free to avoid the tree-sitter 0.21.x hazard where
|
|
17
|
+
* `#match?` / `#eq?` predicates in a top-level `[...]` alternation silently
|
|
18
|
+
* drop sibling-branch matches (see group-layer `JAVA_ROUTE_ANNOTATION_PATTERNS`
|
|
19
|
+
* header comment for details).
|
|
20
|
+
*/
|
|
21
|
+
import Parser from 'tree-sitter';
|
|
22
|
+
import type { ExtractedDecoratorRoute } from '../workers/parse-worker.js';
|
|
23
|
+
/**
|
|
24
|
+
* Extract Spring route annotations from a parsed Java file.
|
|
25
|
+
*
|
|
26
|
+
* Uses a single tree-sitter query pass to capture all annotations, then
|
|
27
|
+
* discriminates class-level prefixes from method-level routes in a loop.
|
|
28
|
+
* Handles multiple classes per file, each with its own prefix.
|
|
29
|
+
*
|
|
30
|
+
* @param tree - tree-sitter parse tree
|
|
31
|
+
* @param filePath - relative file path (for `ExtractedDecoratorRoute.filePath`)
|
|
32
|
+
* @param lineOffset - line offset for pre-processing (usually 0)
|
|
33
|
+
* @returns Decorator routes with prefix already set per-class
|
|
34
|
+
*/
|
|
35
|
+
export declare function extractSpringRoutes(tree: Parser.Tree, filePath: string, lineOffset?: number): ExtractedDecoratorRoute[];
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spring route annotation extractor for the ingestion pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Extracts `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`,
|
|
5
|
+
* `@PatchMapping`, and `@RequestMapping` annotations from Java source files
|
|
6
|
+
* and returns `ExtractedDecoratorRoute[]` with class-level `@RequestMapping`
|
|
7
|
+
* prefixes already resolved per-class.
|
|
8
|
+
*
|
|
9
|
+
* This module is the ingestion-layer counterpart of
|
|
10
|
+
* `group/extractors/http-patterns/java.ts` (which extracts HTTP contracts
|
|
11
|
+
* for cross-repo matching). It uses the same tree-sitter capture approach:
|
|
12
|
+
* a single predicate-free query matches all route annotations generically,
|
|
13
|
+
* then a for-loop discriminates class-level prefixes from method-level routes
|
|
14
|
+
* by reading `@node.type` and the annotation name.
|
|
15
|
+
*
|
|
16
|
+
* The query is predicate-free to avoid the tree-sitter 0.21.x hazard where
|
|
17
|
+
* `#match?` / `#eq?` predicates in a top-level `[...]` alternation silently
|
|
18
|
+
* drop sibling-branch matches (see group-layer `JAVA_ROUTE_ANNOTATION_PATTERNS`
|
|
19
|
+
* header comment for details).
|
|
20
|
+
*/
|
|
21
|
+
import Parser from 'tree-sitter';
|
|
22
|
+
import Java from 'tree-sitter-java';
|
|
23
|
+
import { METHOD_ANNOTATION_TO_HTTP, isRouteMemberKey, findEnclosingClass, unquoteSpringLiteral, } from './spring-shared.js';
|
|
24
|
+
/**
|
|
25
|
+
* Single predicate-free tree-sitter query that captures all route annotations
|
|
26
|
+
* on classes and methods. Discrimination by annotation name and node type
|
|
27
|
+
* happens in the loop below.
|
|
28
|
+
*
|
|
29
|
+
* Captures:
|
|
30
|
+
* @ann → annotation name identifier (RequestMapping, GetMapping, etc.)
|
|
31
|
+
* @node → enclosing declaration (class_declaration | method_declaration)
|
|
32
|
+
* @value → the string-literal argument
|
|
33
|
+
* @key → the named-argument member key (absent for positional form)
|
|
34
|
+
*/
|
|
35
|
+
const ROUTE_ANNOTATION_QUERY = new Parser.Query(Java, `
|
|
36
|
+
[
|
|
37
|
+
(class_declaration
|
|
38
|
+
(modifiers
|
|
39
|
+
(annotation
|
|
40
|
+
name: (identifier) @ann
|
|
41
|
+
arguments: (annotation_argument_list (string_literal) @value)))) @node
|
|
42
|
+
(class_declaration
|
|
43
|
+
(modifiers
|
|
44
|
+
(annotation
|
|
45
|
+
name: (identifier) @ann
|
|
46
|
+
arguments: (annotation_argument_list
|
|
47
|
+
(element_value_pair
|
|
48
|
+
key: (identifier) @key
|
|
49
|
+
value: (string_literal) @value))))) @node
|
|
50
|
+
(method_declaration
|
|
51
|
+
(modifiers
|
|
52
|
+
(annotation
|
|
53
|
+
name: (identifier) @ann
|
|
54
|
+
arguments: (annotation_argument_list (string_literal) @value)))) @node
|
|
55
|
+
(method_declaration
|
|
56
|
+
(modifiers
|
|
57
|
+
(annotation
|
|
58
|
+
name: (identifier) @ann
|
|
59
|
+
arguments: (annotation_argument_list
|
|
60
|
+
(element_value_pair
|
|
61
|
+
key: (identifier) @key
|
|
62
|
+
value: (string_literal) @value))))) @node
|
|
63
|
+
]
|
|
64
|
+
`);
|
|
65
|
+
/**
|
|
66
|
+
* Extract Spring route annotations from a parsed Java file.
|
|
67
|
+
*
|
|
68
|
+
* Uses a single tree-sitter query pass to capture all annotations, then
|
|
69
|
+
* discriminates class-level prefixes from method-level routes in a loop.
|
|
70
|
+
* Handles multiple classes per file, each with its own prefix.
|
|
71
|
+
*
|
|
72
|
+
* @param tree - tree-sitter parse tree
|
|
73
|
+
* @param filePath - relative file path (for `ExtractedDecoratorRoute.filePath`)
|
|
74
|
+
* @param lineOffset - line offset for pre-processing (usually 0)
|
|
75
|
+
* @returns Decorator routes with prefix already set per-class
|
|
76
|
+
*/
|
|
77
|
+
export function extractSpringRoutes(tree, filePath, lineOffset = 0) {
|
|
78
|
+
const matches = ROUTE_ANNOTATION_QUERY.matches(tree.rootNode);
|
|
79
|
+
// Phase 1: collect class-level @RequestMapping prefixes keyed by node id
|
|
80
|
+
const prefixByClassId = new Map();
|
|
81
|
+
for (const match of matches) {
|
|
82
|
+
const caps = {};
|
|
83
|
+
for (const { name, node } of match.captures) {
|
|
84
|
+
caps[name] = node;
|
|
85
|
+
}
|
|
86
|
+
const annNode = caps['ann'];
|
|
87
|
+
const node = caps['node'];
|
|
88
|
+
const valueNode = caps['value'];
|
|
89
|
+
const keyNode = caps['key'];
|
|
90
|
+
if (!annNode || !node || !valueNode)
|
|
91
|
+
continue;
|
|
92
|
+
if (node.type === 'class_declaration' && annNode.text === 'RequestMapping') {
|
|
93
|
+
if (!isRouteMemberKey(keyNode))
|
|
94
|
+
continue;
|
|
95
|
+
const prefix = unquoteSpringLiteral(valueNode.text);
|
|
96
|
+
if (prefix !== null)
|
|
97
|
+
prefixByClassId.set(node.id, prefix);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Phase 2: collect method-level routes and resolve their class prefix
|
|
101
|
+
const routes = [];
|
|
102
|
+
for (const match of matches) {
|
|
103
|
+
const caps = {};
|
|
104
|
+
for (const { name, node } of match.captures) {
|
|
105
|
+
caps[name] = node;
|
|
106
|
+
}
|
|
107
|
+
const annNode = caps['ann'];
|
|
108
|
+
const node = caps['node'];
|
|
109
|
+
const valueNode = caps['value'];
|
|
110
|
+
const keyNode = caps['key'];
|
|
111
|
+
if (!annNode || !node || !valueNode)
|
|
112
|
+
continue;
|
|
113
|
+
if (node.type !== 'method_declaration')
|
|
114
|
+
continue;
|
|
115
|
+
const ann = annNode.text;
|
|
116
|
+
const httpMethod = METHOD_ANNOTATION_TO_HTTP[ann];
|
|
117
|
+
if (!httpMethod)
|
|
118
|
+
continue; // skip @RequestMapping on methods (ambiguous verb)
|
|
119
|
+
if (!isRouteMemberKey(keyNode))
|
|
120
|
+
continue;
|
|
121
|
+
const routePath = unquoteSpringLiteral(valueNode.text);
|
|
122
|
+
if (routePath === null)
|
|
123
|
+
continue;
|
|
124
|
+
const enclosingClass = findEnclosingClass(node);
|
|
125
|
+
const classPrefix = enclosingClass ? (prefixByClassId.get(enclosingClass.id) ?? '') : '';
|
|
126
|
+
routes.push({
|
|
127
|
+
filePath,
|
|
128
|
+
routePath,
|
|
129
|
+
httpMethod,
|
|
130
|
+
decoratorName: ann,
|
|
131
|
+
lineNumber: annNode.startPosition.row + lineOffset,
|
|
132
|
+
...(classPrefix ? { prefix: classPrefix } : {}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return routes;
|
|
136
|
+
}
|
|
@@ -399,6 +399,7 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
|
|
|
399
399
|
const returnType = match['@declaration.return-type']?.text;
|
|
400
400
|
const templateConstraints = parseJsonCapture(match['@declaration.template-constraints']);
|
|
401
401
|
const isExplicit = parseBooleanCapture(match['@declaration.is-explicit']);
|
|
402
|
+
const isDeleted = parseBooleanCapture(match['@declaration.is-deleted']);
|
|
402
403
|
return {
|
|
403
404
|
nodeId: makeDefId(filePath, anchor.range, type, nameCap.text),
|
|
404
405
|
filePath,
|
|
@@ -413,6 +414,7 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
|
|
|
413
414
|
...(templateArguments !== undefined ? { templateArguments } : {}),
|
|
414
415
|
...(templateConstraints !== undefined ? { templateConstraints } : {}),
|
|
415
416
|
...(isExplicit === true ? { isExplicit: true } : {}),
|
|
417
|
+
...(isDeleted === true ? { isDeleted: true } : {}),
|
|
416
418
|
};
|
|
417
419
|
}
|
|
418
420
|
/** Parse an opaque JSON payload synthesized by per-language captures
|
|
@@ -922,6 +924,7 @@ const KNOWN_SUB_TAGS = new Set([
|
|
|
922
924
|
'@declaration.return-type',
|
|
923
925
|
'@declaration.template-constraints',
|
|
924
926
|
'@declaration.is-explicit',
|
|
927
|
+
'@declaration.is-deleted',
|
|
925
928
|
]);
|
|
926
929
|
/**
|
|
927
930
|
* Return the anchor capture for a match — the one whose name begins with
|
|
@@ -289,6 +289,18 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
|
|
|
289
289
|
}
|
|
290
290
|
if (fnDef === undefined)
|
|
291
291
|
continue;
|
|
292
|
+
if (fnDef.isDeleted === true) {
|
|
293
|
+
recordSuppressedOutcome(options.recordResolutionOutcome, {
|
|
294
|
+
phase: 'free-call-fallback',
|
|
295
|
+
filePath: parsed.filePath,
|
|
296
|
+
name: site.name,
|
|
297
|
+
range: site.atRange,
|
|
298
|
+
reason: 'selected-callable-deleted',
|
|
299
|
+
candidates: [fnDef],
|
|
300
|
+
});
|
|
301
|
+
handledSites.add(siteKey(parsed.filePath, site));
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
292
304
|
if ((fnDefFromImplicitThis || fnDef.type === 'Method' || fnDef.type === 'Constructor') &&
|
|
293
305
|
options.isCallableVisibleFromCaller !== undefined &&
|
|
294
306
|
!options.isCallableVisibleFromCaller({
|
|
@@ -138,9 +138,12 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
138
138
|
return 0;
|
|
139
139
|
let n = 0;
|
|
140
140
|
for (const implDef of impls) {
|
|
141
|
-
const implMember =
|
|
142
|
-
if (implMember === undefined
|
|
141
|
+
const implMember = pickOverload(implDef.nodeId, memberName, site, model, provider);
|
|
142
|
+
if (implMember === undefined ||
|
|
143
|
+
implMember === OVERLOAD_AMBIGUOUS ||
|
|
144
|
+
implMember.isDeleted === true) {
|
|
143
145
|
continue;
|
|
146
|
+
}
|
|
144
147
|
if (implMember.nodeId === primaryMemberDef.nodeId)
|
|
145
148
|
continue;
|
|
146
149
|
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, implMember, 'interface-dispatch', seen, confidence, collapse);
|
|
@@ -179,12 +182,30 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
179
182
|
? extendsOnly(enclosingClass.nodeId)
|
|
180
183
|
: scopes.methodDispatch.mroFor(enclosingClass.nodeId);
|
|
181
184
|
let memberDef;
|
|
185
|
+
let ambiguousOwnerId;
|
|
182
186
|
for (const ownerId of ancestors) {
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
const picked = site.kind === 'call'
|
|
188
|
+
? pickOverload(ownerId, memberName, site, model, provider)
|
|
189
|
+
: findOwnedMember(ownerId, memberName, model);
|
|
190
|
+
if (picked === OVERLOAD_AMBIGUOUS) {
|
|
191
|
+
ambiguousOwnerId = ownerId;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
if (picked !== undefined) {
|
|
195
|
+
memberDef = picked;
|
|
185
196
|
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (ambiguousOwnerId !== undefined) {
|
|
200
|
+
recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
|
|
201
|
+
handledSites.add(siteKey);
|
|
202
|
+
continue;
|
|
186
203
|
}
|
|
187
204
|
if (memberDef !== undefined) {
|
|
205
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
|
|
206
|
+
handledSites.add(siteKey);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
188
209
|
// Super/base calls resolve through the MRO chain, not
|
|
189
210
|
// through imports — the ancestor method is found by
|
|
190
211
|
// walking `methodDispatch.mroFor(enclosingClass)`, which
|
|
@@ -219,9 +240,9 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
219
240
|
if (currentClass !== undefined) {
|
|
220
241
|
const chain = [currentClass.nodeId, ...scopes.methodDispatch.mroFor(currentClass.nodeId)];
|
|
221
242
|
let memberDef;
|
|
243
|
+
let ambiguousOwnerId;
|
|
222
244
|
// Static-only filter (#1756 / U3): same shape as Case 4's
|
|
223
|
-
// chain walk (skip-and-walk-on)
|
|
224
|
-
// narrowing — Case 0 uses `findOwnedMember` directly. When
|
|
245
|
+
// overload-aware chain walk (skip-and-walk-on). When
|
|
225
246
|
// an owner's resolved candidate is static-only (Kotlin
|
|
226
247
|
// companion-promoted), continue to the next ancestor in
|
|
227
248
|
// the MRO chain so a legitimate instance member can bind.
|
|
@@ -233,17 +254,29 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
233
254
|
// shapes like `Logger.create("a")`), so there's no wrong
|
|
234
255
|
// target to suppress.
|
|
235
256
|
for (const ownerId of chain) {
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
|
|
257
|
+
const picked = site.kind === 'call'
|
|
258
|
+
? pickFirstNonStaticOnly(ownerId, memberName, site, model, provider)
|
|
259
|
+
: findOwnedMember(ownerId, memberName, model);
|
|
260
|
+
if (picked === OVERLOAD_AMBIGUOUS) {
|
|
261
|
+
ambiguousOwnerId = ownerId;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
if (picked === STATIC_ONLY_FILTERED || picked === undefined) {
|
|
241
265
|
continue;
|
|
242
266
|
}
|
|
243
|
-
memberDef =
|
|
267
|
+
memberDef = picked;
|
|
244
268
|
break;
|
|
245
269
|
}
|
|
270
|
+
if (ambiguousOwnerId !== undefined) {
|
|
271
|
+
recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
|
|
272
|
+
handledSites.add(siteKey);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
246
275
|
if (memberDef !== undefined) {
|
|
276
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
|
|
277
|
+
handledSites.add(siteKey);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
247
280
|
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
|
|
248
281
|
if (ok)
|
|
249
282
|
emitted++;
|
|
@@ -290,6 +323,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
290
323
|
}
|
|
291
324
|
if (languageResolution?.kind === 'resolved') {
|
|
292
325
|
const memberDef = languageResolution.definition;
|
|
326
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
|
|
327
|
+
handledSites.add(siteKey);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
293
330
|
const reason = site.kind === 'write' || site.kind === 'read'
|
|
294
331
|
? site.kind
|
|
295
332
|
: memberDef.filePath !== parsed.filePath
|
|
@@ -357,6 +394,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
357
394
|
continue;
|
|
358
395
|
}
|
|
359
396
|
if (memberDef !== undefined) {
|
|
397
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
|
|
398
|
+
handledSites.add(siteKey);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
360
401
|
const reason = site.kind === 'write' || site.kind === 'read'
|
|
361
402
|
? site.kind
|
|
362
403
|
: memberDef.filePath !== parsed.filePath
|
|
@@ -373,11 +414,16 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
373
414
|
}
|
|
374
415
|
// ── Case 1: namespace receiver ───────────────────────────────
|
|
375
416
|
const targetFiles = namespaceTargets.get(receiverName);
|
|
376
|
-
if (targetFiles !== undefined) {
|
|
417
|
+
if (targetFiles !== undefined && provider.resolveQualifiedReceiverMember === undefined) {
|
|
377
418
|
let found = false;
|
|
378
419
|
for (const targetFile of targetFiles) {
|
|
379
420
|
const memberDef = findExportedDef(targetFile, memberName, index);
|
|
380
421
|
if (memberDef !== undefined) {
|
|
422
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
|
|
423
|
+
handledSites.add(siteKey);
|
|
424
|
+
found = true;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
381
427
|
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
|
|
382
428
|
if (ok)
|
|
383
429
|
emitted++;
|
|
@@ -413,6 +459,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
413
459
|
continue;
|
|
414
460
|
}
|
|
415
461
|
if (memberDef !== undefined) {
|
|
462
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
|
|
463
|
+
handledSites.add(siteKey);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
416
466
|
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
|
|
417
467
|
if (ok)
|
|
418
468
|
emitted++;
|
|
@@ -425,9 +475,17 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
425
475
|
if (classDef !== undefined) {
|
|
426
476
|
const chain = [classDef.nodeId, ...scopes.methodDispatch.mroFor(classDef.nodeId)];
|
|
427
477
|
let memberDef;
|
|
478
|
+
let ambiguousOwnerId;
|
|
428
479
|
for (const ownerId of chain) {
|
|
429
|
-
|
|
430
|
-
|
|
480
|
+
const picked = site.kind === 'call'
|
|
481
|
+
? pickOverload(ownerId, memberName, site, model, provider)
|
|
482
|
+
: findOwnedMember(ownerId, memberName, model);
|
|
483
|
+
if (picked === OVERLOAD_AMBIGUOUS) {
|
|
484
|
+
ambiguousOwnerId = ownerId;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
if (picked !== undefined) {
|
|
488
|
+
memberDef = picked;
|
|
431
489
|
// The MRO chain is most-derived-first ([classDef, ...ancestors]).
|
|
432
490
|
// If the most-derived definition is arity-incompatible with the
|
|
433
491
|
// call site, PHP throws ArgumentCountError at runtime — it does
|
|
@@ -441,7 +499,16 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
441
499
|
break;
|
|
442
500
|
}
|
|
443
501
|
}
|
|
502
|
+
if (ambiguousOwnerId !== undefined) {
|
|
503
|
+
recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
|
|
504
|
+
handledSites.add(siteKey);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
444
507
|
if (memberDef !== undefined) {
|
|
508
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
|
|
509
|
+
handledSites.add(siteKey);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
445
512
|
const reason = site.kind === 'write' || site.kind === 'read'
|
|
446
513
|
? site.kind
|
|
447
514
|
: memberDef.filePath !== parsed.filePath
|
|
@@ -466,8 +533,22 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
466
533
|
for (const targetFile3 of targetFiles3) {
|
|
467
534
|
const classDef3 = findExportedDef(targetFile3, className, index);
|
|
468
535
|
if (classDef3 !== undefined) {
|
|
469
|
-
const
|
|
470
|
-
|
|
536
|
+
const picked = site.kind === 'call'
|
|
537
|
+
? pickOverload(classDef3.nodeId, memberName, site, model, provider)
|
|
538
|
+
: findOwnedMember(classDef3.nodeId, memberName, model);
|
|
539
|
+
if (picked === OVERLOAD_AMBIGUOUS) {
|
|
540
|
+
recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, classDef3.nodeId, memberName, model, provider);
|
|
541
|
+
handledSites.add(siteKey);
|
|
542
|
+
found3 = true;
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
if (picked !== undefined) {
|
|
546
|
+
const memberDef = picked;
|
|
547
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
|
|
548
|
+
handledSites.add(siteKey);
|
|
549
|
+
found3 = true;
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
471
552
|
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen);
|
|
472
553
|
if (ok) {
|
|
473
554
|
emitted++;
|
|
@@ -504,8 +585,9 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
504
585
|
if (ownerDef !== undefined) {
|
|
505
586
|
const chain = [ownerDef.nodeId, ...scopes.methodDispatch.mroFor(ownerDef.nodeId)];
|
|
506
587
|
let memberDef;
|
|
507
|
-
|
|
508
|
-
//
|
|
588
|
+
let ambiguousOwnerId;
|
|
589
|
+
// Static-only filter (#1756 / U3): mirrors Case 0's
|
|
590
|
+
// overload-aware chain walk. When
|
|
509
591
|
// a static-only candidate is found at an ancestor, walk on
|
|
510
592
|
// so a legitimate instance member can bind. If the entire
|
|
511
593
|
// chain is static-only, no edge is emitted (Case 3b is fed
|
|
@@ -513,16 +595,29 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
513
595
|
// `emitReferencesViaLookup` for compound shapes, so no
|
|
514
596
|
// handled-site marker is needed for chain-only-static).
|
|
515
597
|
for (const ownerId of chain) {
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
if (
|
|
598
|
+
const picked = site.kind === 'call'
|
|
599
|
+
? pickFirstNonStaticOnly(ownerId, memberName, site, model, provider)
|
|
600
|
+
: findOwnedMember(ownerId, memberName, model);
|
|
601
|
+
if (picked === OVERLOAD_AMBIGUOUS) {
|
|
602
|
+
ambiguousOwnerId = ownerId;
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
if (picked === STATIC_ONLY_FILTERED || picked === undefined) {
|
|
520
606
|
continue;
|
|
521
607
|
}
|
|
522
|
-
memberDef =
|
|
608
|
+
memberDef = picked;
|
|
523
609
|
break;
|
|
524
610
|
}
|
|
611
|
+
if (ambiguousOwnerId !== undefined) {
|
|
612
|
+
recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
|
|
613
|
+
handledSites.add(siteKey);
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
525
616
|
if (memberDef !== undefined) {
|
|
617
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
|
|
618
|
+
handledSites.add(siteKey);
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
526
621
|
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
|
|
527
622
|
if (ok)
|
|
528
623
|
emitted++;
|
|
@@ -574,6 +669,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
574
669
|
}
|
|
575
670
|
if (languageResolution?.kind === 'resolved') {
|
|
576
671
|
const memberDef = languageResolution.definition;
|
|
672
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
|
|
673
|
+
handledSites.add(siteKey);
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
577
676
|
const reason = site.kind === 'write' || site.kind === 'read'
|
|
578
677
|
? site.kind
|
|
579
678
|
: memberDef.filePath !== parsed.filePath
|
|
@@ -657,6 +756,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
657
756
|
continue;
|
|
658
757
|
}
|
|
659
758
|
if (memberDef !== undefined) {
|
|
759
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
|
|
760
|
+
handledSites.add(siteKey);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
660
763
|
// For read/write ACCESSES, mirror the legacy DAG's reason
|
|
661
764
|
// convention so consumers asserting `reason === 'write'`
|
|
662
765
|
// keep working.
|
|
@@ -713,6 +816,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
713
816
|
continue;
|
|
714
817
|
}
|
|
715
818
|
if (picked !== undefined) {
|
|
819
|
+
if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, picked)) {
|
|
820
|
+
handledSites.add(siteKey);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
716
823
|
// Static-only filter (#1756 / U3): unlike Case 4 there's no
|
|
717
824
|
// MRO chain to walk here — Case 5 dispatches on a single
|
|
718
825
|
// owner via `pickOverload`. When the picked candidate is
|
|
@@ -873,6 +980,20 @@ function pickFirstNonStaticOnly(ownerId, memberName, site, model, provider) {
|
|
|
873
980
|
return OVERLOAD_AMBIGUOUS;
|
|
874
981
|
return candidates[0] ?? overloads[0];
|
|
875
982
|
}
|
|
983
|
+
function suppressDeletedCallTarget(record, filePath, site, target) {
|
|
984
|
+
if (site.kind !== 'call' || target.isDeleted !== true)
|
|
985
|
+
return false;
|
|
986
|
+
record?.({
|
|
987
|
+
kind: 'suppressed',
|
|
988
|
+
phase: 'receiver-bound-calls',
|
|
989
|
+
filePath,
|
|
990
|
+
name: site.name,
|
|
991
|
+
range: site.atRange,
|
|
992
|
+
reason: 'selected-callable-deleted',
|
|
993
|
+
candidateIds: [target.nodeId],
|
|
994
|
+
});
|
|
995
|
+
return true;
|
|
996
|
+
}
|
|
876
997
|
function recordReceiverOverloadSuppression(record, filePath, site, ownerId, memberName, model, provider) {
|
|
877
998
|
if (record === undefined)
|
|
878
999
|
return;
|