seer-mcp 0.1.0
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/.vscode/settings.json +3 -0
- package/LICENSE +176 -0
- package/README.md +272 -0
- package/README_dev.md +199 -0
- package/dist/bundle/ci.d.ts +47 -0
- package/dist/bundle/ci.d.ts.map +1 -0
- package/dist/bundle/ci.js +113 -0
- package/dist/bundle/ci.js.map +1 -0
- package/dist/bundle/contract.d.ts +111 -0
- package/dist/bundle/contract.d.ts.map +1 -0
- package/dist/bundle/contract.js +352 -0
- package/dist/bundle/contract.js.map +1 -0
- package/dist/bundle/export.d.ts +36 -0
- package/dist/bundle/export.d.ts.map +1 -0
- package/dist/bundle/export.js +152 -0
- package/dist/bundle/export.js.map +1 -0
- package/dist/bundle/external.d.ts +66 -0
- package/dist/bundle/external.d.ts.map +1 -0
- package/dist/bundle/external.js +238 -0
- package/dist/bundle/external.js.map +1 -0
- package/dist/bundle/format.d.ts +94 -0
- package/dist/bundle/format.d.ts.map +1 -0
- package/dist/bundle/format.js +42 -0
- package/dist/bundle/format.js.map +1 -0
- package/dist/bundle/import.d.ts +49 -0
- package/dist/bundle/import.d.ts.map +1 -0
- package/dist/bundle/import.js +116 -0
- package/dist/bundle/import.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1402 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +48 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +284 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/db/schema.d.ts +3 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +616 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/store.d.ts +1011 -0
- package/dist/db/store.d.ts.map +1 -0
- package/dist/db/store.js +3888 -0
- package/dist/db/store.js.map +1 -0
- package/dist/graph/pagerank.d.ts +9 -0
- package/dist/graph/pagerank.d.ts.map +1 -0
- package/dist/graph/pagerank.js +47 -0
- package/dist/graph/pagerank.js.map +1 -0
- package/dist/indexer/architecture.d.ts +72 -0
- package/dist/indexer/architecture.d.ts.map +1 -0
- package/dist/indexer/architecture.js +112 -0
- package/dist/indexer/architecture.js.map +1 -0
- package/dist/indexer/behavior.d.ts +75 -0
- package/dist/indexer/behavior.d.ts.map +1 -0
- package/dist/indexer/behavior.js +395 -0
- package/dist/indexer/behavior.js.map +1 -0
- package/dist/indexer/boundaries.d.ts +60 -0
- package/dist/indexer/boundaries.d.ts.map +1 -0
- package/dist/indexer/boundaries.js +366 -0
- package/dist/indexer/boundaries.js.map +1 -0
- package/dist/indexer/churn.d.ts +15 -0
- package/dist/indexer/churn.d.ts.map +1 -0
- package/dist/indexer/churn.js +49 -0
- package/dist/indexer/churn.js.map +1 -0
- package/dist/indexer/classify.d.ts +9 -0
- package/dist/indexer/classify.d.ts.map +1 -0
- package/dist/indexer/classify.js +90 -0
- package/dist/indexer/classify.js.map +1 -0
- package/dist/indexer/context.d.ts +176 -0
- package/dist/indexer/context.d.ts.map +1 -0
- package/dist/indexer/context.js +193 -0
- package/dist/indexer/context.js.map +1 -0
- package/dist/indexer/continuity.d.ts +67 -0
- package/dist/indexer/continuity.d.ts.map +1 -0
- package/dist/indexer/continuity.js +288 -0
- package/dist/indexer/continuity.js.map +1 -0
- package/dist/indexer/detectchanges.d.ts +32 -0
- package/dist/indexer/detectchanges.d.ts.map +1 -0
- package/dist/indexer/detectchanges.js +74 -0
- package/dist/indexer/detectchanges.js.map +1 -0
- package/dist/indexer/discovery.d.ts +37 -0
- package/dist/indexer/discovery.d.ts.map +1 -0
- package/dist/indexer/discovery.js +136 -0
- package/dist/indexer/discovery.js.map +1 -0
- package/dist/indexer/externaldeps.d.ts +18 -0
- package/dist/indexer/externaldeps.d.ts.map +1 -0
- package/dist/indexer/externaldeps.js +288 -0
- package/dist/indexer/externaldeps.js.map +1 -0
- package/dist/indexer/freshness.d.ts +48 -0
- package/dist/indexer/freshness.d.ts.map +1 -0
- package/dist/indexer/freshness.js +128 -0
- package/dist/indexer/freshness.js.map +1 -0
- package/dist/indexer/git.d.ts +144 -0
- package/dist/indexer/git.d.ts.map +1 -0
- package/dist/indexer/git.js +444 -0
- package/dist/indexer/git.js.map +1 -0
- package/dist/indexer/index.d.ts +145 -0
- package/dist/indexer/index.d.ts.map +1 -0
- package/dist/indexer/index.js +930 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/modules.d.ts +62 -0
- package/dist/indexer/modules.d.ts.map +1 -0
- package/dist/indexer/modules.js +293 -0
- package/dist/indexer/modules.js.map +1 -0
- package/dist/indexer/preflight.d.ts +154 -0
- package/dist/indexer/preflight.d.ts.map +1 -0
- package/dist/indexer/preflight.js +399 -0
- package/dist/indexer/preflight.js.map +1 -0
- package/dist/indexer/protoScanner.d.ts +34 -0
- package/dist/indexer/protoScanner.d.ts.map +1 -0
- package/dist/indexer/protoScanner.js +133 -0
- package/dist/indexer/protoScanner.js.map +1 -0
- package/dist/indexer/risk.d.ts +115 -0
- package/dist/indexer/risk.d.ts.map +1 -0
- package/dist/indexer/risk.js +194 -0
- package/dist/indexer/risk.js.map +1 -0
- package/dist/indexer/serviceHostScanner.d.ts +25 -0
- package/dist/indexer/serviceHostScanner.d.ts.map +1 -0
- package/dist/indexer/serviceHostScanner.js +95 -0
- package/dist/indexer/serviceHostScanner.js.map +1 -0
- package/dist/indexer/serviceLinks.d.ts +105 -0
- package/dist/indexer/serviceLinks.d.ts.map +1 -0
- package/dist/indexer/serviceLinks.js +509 -0
- package/dist/indexer/serviceLinks.js.map +1 -0
- package/dist/indexer/shapehash.d.ts +98 -0
- package/dist/indexer/shapehash.d.ts.map +1 -0
- package/dist/indexer/shapehash.js +354 -0
- package/dist/indexer/shapehash.js.map +1 -0
- package/dist/indexer/skeleton.d.ts +15 -0
- package/dist/indexer/skeleton.d.ts.map +1 -0
- package/dist/indexer/skeleton.js +136 -0
- package/dist/indexer/skeleton.js.map +1 -0
- package/dist/indexer/symbolhistory.d.ts +41 -0
- package/dist/indexer/symbolhistory.d.ts.map +1 -0
- package/dist/indexer/symbolhistory.js +124 -0
- package/dist/indexer/symbolhistory.js.map +1 -0
- package/dist/indexer/watcher.d.ts +68 -0
- package/dist/indexer/watcher.d.ts.map +1 -0
- package/dist/indexer/watcher.js +179 -0
- package/dist/indexer/watcher.js.map +1 -0
- package/dist/mcp/server.d.ts +80 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +1610 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/parser/index.d.ts +8 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +33 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/languages/cpp.d.ts +3 -0
- package/dist/parser/languages/cpp.d.ts.map +1 -0
- package/dist/parser/languages/cpp.js +350 -0
- package/dist/parser/languages/cpp.js.map +1 -0
- package/dist/parser/languages/csharp.d.ts +3 -0
- package/dist/parser/languages/csharp.d.ts.map +1 -0
- package/dist/parser/languages/csharp.js +239 -0
- package/dist/parser/languages/csharp.js.map +1 -0
- package/dist/parser/languages/go.d.ts +3 -0
- package/dist/parser/languages/go.d.ts.map +1 -0
- package/dist/parser/languages/go.js +259 -0
- package/dist/parser/languages/go.js.map +1 -0
- package/dist/parser/languages/java.d.ts +3 -0
- package/dist/parser/languages/java.d.ts.map +1 -0
- package/dist/parser/languages/java.js +391 -0
- package/dist/parser/languages/java.js.map +1 -0
- package/dist/parser/languages/python.d.ts +3 -0
- package/dist/parser/languages/python.d.ts.map +1 -0
- package/dist/parser/languages/python.js +396 -0
- package/dist/parser/languages/python.js.map +1 -0
- package/dist/parser/languages/rust.d.ts +3 -0
- package/dist/parser/languages/rust.d.ts.map +1 -0
- package/dist/parser/languages/rust.js +159 -0
- package/dist/parser/languages/rust.js.map +1 -0
- package/dist/parser/languages/typescript.d.ts +3 -0
- package/dist/parser/languages/typescript.d.ts.map +1 -0
- package/dist/parser/languages/typescript.js +1442 -0
- package/dist/parser/languages/typescript.js.map +1 -0
- package/dist/parser/parserContext.d.ts +77 -0
- package/dist/parser/parserContext.d.ts.map +1 -0
- package/dist/parser/parserContext.js +354 -0
- package/dist/parser/parserContext.js.map +1 -0
- package/dist/parser/walker.d.ts +81 -0
- package/dist/parser/walker.d.ts.map +1 -0
- package/dist/parser/walker.js +217 -0
- package/dist/parser/walker.js.map +1 -0
- package/dist/parser/worker.d.ts +66 -0
- package/dist/parser/worker.d.ts.map +1 -0
- package/dist/parser/worker.js +129 -0
- package/dist/parser/worker.js.map +1 -0
- package/dist/parser/workerpool.d.ts +107 -0
- package/dist/parser/workerpool.d.ts.map +1 -0
- package/dist/parser/workerpool.js +383 -0
- package/dist/parser/workerpool.js.map +1 -0
- package/dist/scip/format.d.ts +87 -0
- package/dist/scip/format.d.ts.map +1 -0
- package/dist/scip/format.js +31 -0
- package/dist/scip/format.js.map +1 -0
- package/dist/scip/import.d.ts +37 -0
- package/dist/scip/import.d.ts.map +1 -0
- package/dist/scip/import.js +180 -0
- package/dist/scip/import.js.map +1 -0
- package/dist/types.d.ts +392 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/docs/architecture.md +105 -0
- package/docs/benchmarks/methodology.md +134 -0
- package/docs/benchmarks/raw-results.md +71 -0
- package/docs/benchmarks.md +74 -0
- package/docs/cli.md +148 -0
- package/docs/examples/behavior-tests.md +70 -0
- package/docs/examples/change-history.md +85 -0
- package/docs/examples/pre-edit-context.md +81 -0
- package/docs/examples/service-links.md +88 -0
- package/docs/examples.md +80 -0
- package/docs/faq.md +70 -0
- package/docs/internals.md +104 -0
- package/docs/languages.md +70 -0
- package/docs/limits.md +52 -0
- package/docs/mcp.md +199 -0
- package/docs/quickstart.md +119 -0
- package/docs/testing.md +123 -0
- package/docs/tools.md +115 -0
- package/package.json +52 -0
- package/research-codebase.md +578 -0
- package/seer-cli-docs.md +326 -0
- package/seer-master-guide.md +246 -0
- package/src/bundle/ci.ts +141 -0
- package/src/bundle/contract.ts +387 -0
- package/src/bundle/export.ts +175 -0
- package/src/bundle/external.ts +285 -0
- package/src/bundle/format.ts +92 -0
- package/src/bundle/import.ts +157 -0
- package/src/cli/index.ts +1249 -0
- package/src/cli/init.ts +389 -0
- package/src/db/schema.ts +614 -0
- package/src/db/store.ts +4306 -0
- package/src/graph/pagerank.ts +53 -0
- package/src/indexer/architecture.ts +148 -0
- package/src/indexer/behavior.ts +466 -0
- package/src/indexer/boundaries.ts +374 -0
- package/src/indexer/churn.ts +58 -0
- package/src/indexer/classify.ts +96 -0
- package/src/indexer/context.ts +340 -0
- package/src/indexer/continuity.ts +322 -0
- package/src/indexer/detectchanges.ts +94 -0
- package/src/indexer/discovery.ts +176 -0
- package/src/indexer/externaldeps.ts +243 -0
- package/src/indexer/freshness.ts +166 -0
- package/src/indexer/git.ts +453 -0
- package/src/indexer/index.ts +1092 -0
- package/src/indexer/modules.ts +358 -0
- package/src/indexer/preflight.ts +548 -0
- package/src/indexer/protoScanner.ts +147 -0
- package/src/indexer/risk.ts +304 -0
- package/src/indexer/serviceHostScanner.ts +92 -0
- package/src/indexer/serviceLinks.ts +543 -0
- package/src/indexer/shapehash.ts +370 -0
- package/src/indexer/skeleton.ts +169 -0
- package/src/indexer/symbolhistory.ts +172 -0
- package/src/indexer/watcher.ts +206 -0
- package/src/mcp/server.ts +1659 -0
- package/src/parser/index.ts +37 -0
- package/src/parser/languages/cpp.ts +361 -0
- package/src/parser/languages/csharp.ts +235 -0
- package/src/parser/languages/go.ts +259 -0
- package/src/parser/languages/java.ts +382 -0
- package/src/parser/languages/python.ts +370 -0
- package/src/parser/languages/rust.ts +164 -0
- package/src/parser/languages/typescript.ts +1435 -0
- package/src/parser/parserContext.ts +392 -0
- package/src/parser/walker.ts +306 -0
- package/src/parser/worker.ts +181 -0
- package/src/parser/workerpool.ts +448 -0
- package/src/scip/format.ts +83 -0
- package/src/scip/import.ts +216 -0
- package/src/types.ts +457 -0
- package/tests/benchmark-service-links.ts +244 -0
- package/tests/bug-regressions.ts +626 -0
- package/tests/filters.ts +264 -0
- package/tests/fixtures/Counter.tsx +38 -0
- package/tests/fixtures/caller.ts +7 -0
- package/tests/fixtures/collisions.ts +23 -0
- package/tests/fixtures/local_helper.ts +5 -0
- package/tests/fixtures/overloads.java +17 -0
- package/tests/fixtures/remote_helper.ts +4 -0
- package/tests/fixtures/sample.c +15 -0
- package/tests/fixtures/sample.cpp +47 -0
- package/tests/fixtures/sample.cs +62 -0
- package/tests/fixtures/sample.go +68 -0
- package/tests/fixtures/sample.h +30 -0
- package/tests/fixtures/sample.java +85 -0
- package/tests/fixtures/sample.py +46 -0
- package/tests/fixtures/sample.rs +78 -0
- package/tests/fixtures/sample.ts +76 -0
- package/tests/fixtures-service/HttpClients.cs +30 -0
- package/tests/fixtures-service/HttpClients.java +24 -0
- package/tests/fixtures-service/billing.ts +15 -0
- package/tests/fixtures-service/docker-compose.yml +15 -0
- package/tests/fixtures-service/gateway.ts +10 -0
- package/tests/fixtures-service/get_user.ts +11 -0
- package/tests/fixtures-service/graphql_client.ts +63 -0
- package/tests/fixtures-service/graphql_server.ts +30 -0
- package/tests/fixtures-service/grpc_client.go +30 -0
- package/tests/fixtures-service/http_clients.go +23 -0
- package/tests/fixtures-service/http_clients.py +38 -0
- package/tests/fixtures-service/http_clients.ts +49 -0
- package/tests/fixtures-service/k8s/payment-service.yaml +22 -0
- package/tests/fixtures-service/k8s_calls.ts +20 -0
- package/tests/fixtures-service/messaging.ts +87 -0
- package/tests/fixtures-service/trpc_client.ts +39 -0
- package/tests/fixtures-service/trpc_server.ts +39 -0
- package/tests/fixtures-service/user_service.proto +33 -0
- package/tests/fixtures-trackcd/Cargo.toml +11 -0
- package/tests/fixtures-trackcd/SpringController.java +36 -0
- package/tests/fixtures-trackcd/auth_service.ts +19 -0
- package/tests/fixtures-trackcd/complex_module.py +50 -0
- package/tests/fixtures-trackcd/express_app.js +30 -0
- package/tests/fixtures-trackcd/fastapi_app.py +49 -0
- package/tests/fixtures-trackcd/fastify_object_routes.js +32 -0
- package/tests/fixtures-trackcd/go.mod +8 -0
- package/tests/fixtures-trackcd/package.json +15 -0
- package/tests/fixtures-trackcd/requirements.txt +4 -0
- package/tests/fixtures-trackcd/tests/auth_service.test.ts +13 -0
- package/tests/fixtures-tracke/auth/AuthService.ts +23 -0
- package/tests/fixtures-tracke/auth/crypto.ts +7 -0
- package/tests/fixtures-tracke/billing/Billing.ts +20 -0
- package/tests/fixtures-tracke/billing/Invoice.ts +10 -0
- package/tests/fixtures-tracke/billing/server.ts +17 -0
- package/tests/fixtures-tracke/package.json +7 -0
- package/tests/fixtures-tracke/tests/auth.test.ts +23 -0
- package/tests/fixtures-tracke/tests/billing.test.ts +14 -0
- package/tests/fixtures-trackf/package.json +5 -0
- package/tests/fixtures-trackf/src/auth.ts +26 -0
- package/tests/fixtures-trackf/src/handlers.ts +35 -0
- package/tests/fixtures-tracki/billing/routes.ts +12 -0
- package/tests/fixtures-tracki/gateway/client.ts +13 -0
- package/tests/git-features.ts +267 -0
- package/tests/init.ts +141 -0
- package/tests/mcp-jit.ts +130 -0
- package/tests/mcp-smoke.ts +191 -0
- package/tests/mcp-trackcd.ts +169 -0
- package/tests/mcp-tracke.ts +229 -0
- package/tests/mcp-trackf.ts +330 -0
- package/tests/mcp-trackg.ts +219 -0
- package/tests/mcp-tracki.ts +174 -0
- package/tests/mcp-watcher.ts +126 -0
- package/tests/optspec.ts +194 -0
- package/tests/parallel-index.ts +333 -0
- package/tests/parallel-read.ts +125 -0
- package/tests/parallel-recovery.ts +241 -0
- package/tests/perf-callers.ts +145 -0
- package/tests/query-parity.ts +184 -0
- package/tests/query-perf.ts +55 -0
- package/tests/scale-parallel-parity.ts +225 -0
- package/tests/scale-test.ts +523 -0
- package/tests/smoke.ts +396 -0
- package/tests/trackcd.ts +325 -0
- package/tests/tracke-collisions.ts +255 -0
- package/tests/tracke.ts +314 -0
- package/tests/trackf-bugs.ts +406 -0
- package/tests/trackf.ts +390 -0
- package/tests/trackg.ts +1372 -0
- package/tests/tracki-boundaries.ts +202 -0
- package/tests/tracki-continuity.ts +253 -0
- package/tests/tracki-contract-diff.ts +249 -0
- package/tests/tracki-external-bundles.ts +341 -0
- package/tests/tracki-preflight.ts +251 -0
- package/tests/verify-roles.ts +51 -0
- package/tests/worker-parity.ts +286 -0
- package/tests/worker-pool.ts +262 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import type Parser from 'web-tree-sitter';
|
|
2
|
+
import type { SymbolDef, ServiceCallDef } from '../../types.js';
|
|
3
|
+
import type { LanguageExtractor } from '../walker.js';
|
|
4
|
+
import { firstLine } from '../walker.js';
|
|
5
|
+
|
|
6
|
+
// Go HTTP-client method names. The receiver may be `http`, `http.DefaultClient`,
|
|
7
|
+
// or any user client (e.g. `myClient`).
|
|
8
|
+
const GO_HTTP_VERBS = new Set([
|
|
9
|
+
'Get', 'Post', 'PostForm', 'Head', 'Do', 'Patch', 'Put', 'Delete', 'NewRequest',
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
const GO_BRANCH_NODES = new Set<string>([
|
|
13
|
+
'if_statement', 'for_statement', 'expression_case', 'default_case',
|
|
14
|
+
'type_case', 'communication_case', 'select_statement',
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const GO_NESTING_NODES = new Set<string>([
|
|
18
|
+
'if_statement', 'for_statement', 'expression_switch_statement',
|
|
19
|
+
'type_switch_statement', 'select_statement',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const GO_CANDIDATE_NODE_TYPES = [
|
|
23
|
+
// tryExtractDefinition
|
|
24
|
+
'function_declaration',
|
|
25
|
+
'method_declaration',
|
|
26
|
+
'type_declaration',
|
|
27
|
+
// tryExtractCallName
|
|
28
|
+
'call_expression',
|
|
29
|
+
// tryExtractImport
|
|
30
|
+
'import_spec',
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export const goExtractor: LanguageExtractor = {
|
|
34
|
+
languageName: 'go',
|
|
35
|
+
extensions: ['.go'],
|
|
36
|
+
branchNodeTypes: GO_BRANCH_NODES,
|
|
37
|
+
nestingNodeTypes: GO_NESTING_NODES,
|
|
38
|
+
candidateNodeTypes: GO_CANDIDATE_NODE_TYPES,
|
|
39
|
+
|
|
40
|
+
tryExtractDefinition(node: Parser.SyntaxNode): SymbolDef | null {
|
|
41
|
+
switch (node.type) {
|
|
42
|
+
case 'function_declaration': {
|
|
43
|
+
const nameNode = node.childForFieldName('name');
|
|
44
|
+
if (!nameNode) return null;
|
|
45
|
+
return {
|
|
46
|
+
name: nameNode.text,
|
|
47
|
+
kind: 'function',
|
|
48
|
+
lineStart: node.startPosition.row,
|
|
49
|
+
lineEnd: node.endPosition.row,
|
|
50
|
+
colStart: node.startPosition.column,
|
|
51
|
+
colEnd: node.endPosition.column,
|
|
52
|
+
signature: firstLine(node),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
case 'method_declaration': {
|
|
57
|
+
const nameNode = node.childForFieldName('name');
|
|
58
|
+
if (!nameNode) return null;
|
|
59
|
+
return {
|
|
60
|
+
name: nameNode.text,
|
|
61
|
+
kind: 'method',
|
|
62
|
+
lineStart: node.startPosition.row,
|
|
63
|
+
lineEnd: node.endPosition.row,
|
|
64
|
+
colStart: node.startPosition.column,
|
|
65
|
+
colEnd: node.endPosition.column,
|
|
66
|
+
signature: firstLine(node),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// type Foo struct {} or type Foo interface {}
|
|
71
|
+
case 'type_declaration': {
|
|
72
|
+
// type_declaration contains one or more type_spec children
|
|
73
|
+
for (const child of node.children) {
|
|
74
|
+
if (child.type === 'type_spec') {
|
|
75
|
+
const nameNode = child.childForFieldName('name');
|
|
76
|
+
if (!nameNode) continue;
|
|
77
|
+
const typeNode = child.childForFieldName('type');
|
|
78
|
+
const kind = typeNode?.type === 'interface_type' ? 'interface'
|
|
79
|
+
: typeNode?.type === 'struct_type' ? 'struct'
|
|
80
|
+
: 'type';
|
|
81
|
+
return {
|
|
82
|
+
name: nameNode.text,
|
|
83
|
+
kind,
|
|
84
|
+
lineStart: node.startPosition.row,
|
|
85
|
+
lineEnd: node.endPosition.row,
|
|
86
|
+
colStart: node.startPosition.column,
|
|
87
|
+
colEnd: node.endPosition.column,
|
|
88
|
+
signature: firstLine(node),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
default:
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
tryExtractCallName(node: Parser.SyntaxNode): string | null {
|
|
101
|
+
if (node.type !== 'call_expression') return null;
|
|
102
|
+
const funcNode = node.childForFieldName('function');
|
|
103
|
+
if (!funcNode) return null;
|
|
104
|
+
|
|
105
|
+
// foo()
|
|
106
|
+
if (funcNode.type === 'identifier') return funcNode.text;
|
|
107
|
+
|
|
108
|
+
// pkg.Func() or receiver.Method()
|
|
109
|
+
if (funcNode.type === 'selector_expression') {
|
|
110
|
+
return funcNode.childForFieldName('field')?.text ?? null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
tryExtractImport(node: Parser.SyntaxNode): string | null {
|
|
117
|
+
// import_spec contains a "path" field (interpreted_string_literal)
|
|
118
|
+
if (node.type === 'import_spec') {
|
|
119
|
+
const pathNode = node.childForFieldName('path');
|
|
120
|
+
return pathNode?.text?.replace(/['"]/g, '') ?? null;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Go HTTP-client calls:
|
|
127
|
+
* http.Get("https://x/y") ← yes
|
|
128
|
+
* http.Post("/api", "json", body) ← yes
|
|
129
|
+
* client.Get("/api/users") ← yes (any receiver, capital verb)
|
|
130
|
+
* http.NewRequest("GET", "/x", …) ← yes (method = first string arg)
|
|
131
|
+
*/
|
|
132
|
+
tryExtractServiceCalls(node: Parser.SyntaxNode): ServiceCallDef[] | null {
|
|
133
|
+
if (node.type !== 'call_expression') return null;
|
|
134
|
+
const fn = node.childForFieldName('function');
|
|
135
|
+
if (!fn || fn.type !== 'selector_expression') return null;
|
|
136
|
+
const recv = fn.childForFieldName('operand');
|
|
137
|
+
const field = fn.childForFieldName('field');
|
|
138
|
+
if (!recv || !field) return null;
|
|
139
|
+
const verb = field.text;
|
|
140
|
+
|
|
141
|
+
// v9 Track-H — gRPC client call:
|
|
142
|
+
// pb.NewUserServiceClient(conn).GetUser(ctx, &req)
|
|
143
|
+
// recv is a call_expression to pb.New<X>ServiceClient(...) or pb.New<X>Client(...);
|
|
144
|
+
// verb is the method (capitalized). We emit a service_call with operation
|
|
145
|
+
// = "Service/Method" matching the .proto resolver.
|
|
146
|
+
if (recv.type === 'call_expression' && verb && /^[A-Z]/.test(verb)) {
|
|
147
|
+
const grpc = tryExtractGoGrpcCall(node, recv, verb);
|
|
148
|
+
if (grpc) return [grpc];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!GO_HTTP_VERBS.has(verb)) return null;
|
|
152
|
+
|
|
153
|
+
let framework: string;
|
|
154
|
+
if (recv.text === 'http') framework = 'http';
|
|
155
|
+
else if (recv.text === 'httputil') framework = 'httputil';
|
|
156
|
+
else framework = 'http-client';
|
|
157
|
+
|
|
158
|
+
const args = node.childForFieldName('arguments');
|
|
159
|
+
if (!args) return null;
|
|
160
|
+
const named = args.namedChildren;
|
|
161
|
+
|
|
162
|
+
let method: string | undefined;
|
|
163
|
+
let urlIdx = 0;
|
|
164
|
+
if (verb === 'NewRequest' || verb === 'NewRequestWithContext') {
|
|
165
|
+
const ctxOffset = verb === 'NewRequestWithContext' ? 1 : 0;
|
|
166
|
+
const methodNode = named[ctxOffset];
|
|
167
|
+
if (methodNode && methodNode.type === 'interpreted_string_literal') {
|
|
168
|
+
method = methodNode.text.replace(/['"`]/g, '').toUpperCase();
|
|
169
|
+
}
|
|
170
|
+
urlIdx = ctxOffset + 1;
|
|
171
|
+
} else if (verb === 'Do') {
|
|
172
|
+
// http.Client.Do(req) doesn't expose the URL here; skip.
|
|
173
|
+
return null;
|
|
174
|
+
} else {
|
|
175
|
+
method = verb === 'Get' ? 'GET'
|
|
176
|
+
: verb === 'Post' ? 'POST'
|
|
177
|
+
: verb === 'PostForm' ? 'POST'
|
|
178
|
+
: verb === 'Head' ? 'HEAD'
|
|
179
|
+
: verb === 'Patch' ? 'PATCH'
|
|
180
|
+
: verb === 'Put' ? 'PUT'
|
|
181
|
+
: verb === 'Delete' ? 'DELETE'
|
|
182
|
+
: 'ANY';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const urlNode = named[urlIdx];
|
|
186
|
+
if (!urlNode) return null;
|
|
187
|
+
let raw: string | null = null;
|
|
188
|
+
if (urlNode.type === 'interpreted_string_literal' || urlNode.type === 'raw_string_literal') {
|
|
189
|
+
raw = urlNode.text.replace(/^[`"']|[`"']$/g, '');
|
|
190
|
+
} else {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
if (!raw || !goLooksLikeHttpTarget(raw)) return null;
|
|
194
|
+
|
|
195
|
+
return [{
|
|
196
|
+
protocol: 'http',
|
|
197
|
+
method: method ?? 'ANY',
|
|
198
|
+
rawTarget: raw.slice(0, 240),
|
|
199
|
+
framework,
|
|
200
|
+
line: node.startPosition.row,
|
|
201
|
+
confidence: 0.85,
|
|
202
|
+
}];
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* v9 Track-H — detect a gRPC client stub call in Go.
|
|
208
|
+
*
|
|
209
|
+
* Pattern: `pb.NewUserServiceClient(conn).GetUser(ctx, &req)`
|
|
210
|
+
*
|
|
211
|
+
* `recv` is the inner call expression (`pb.NewUserServiceClient(conn)`); we
|
|
212
|
+
* look at its callee to harvest the service name from `NewXServiceClient` or
|
|
213
|
+
* `NewXClient`. The outer method (`GetUser`) becomes the rpc method.
|
|
214
|
+
*
|
|
215
|
+
* Returns a ServiceCallDef with:
|
|
216
|
+
* - protocol = 'grpc'
|
|
217
|
+
* - operation = 'Service/Method' (matches .proto-derived routes)
|
|
218
|
+
* - service = 'UserService'
|
|
219
|
+
* - method = 'GetUser'
|
|
220
|
+
*
|
|
221
|
+
* Returns null if the inner callee does not parse as a NewXClient
|
|
222
|
+
* constructor — keeps determinism high (no false-positive on a chained
|
|
223
|
+
* method call against an unrelated builder pattern).
|
|
224
|
+
*/
|
|
225
|
+
function tryExtractGoGrpcCall(
|
|
226
|
+
outer: Parser.SyntaxNode,
|
|
227
|
+
recv: Parser.SyntaxNode,
|
|
228
|
+
rpcMethod: string,
|
|
229
|
+
): ServiceCallDef | null {
|
|
230
|
+
const innerFn = recv.childForFieldName('function');
|
|
231
|
+
if (!innerFn || innerFn.type !== 'selector_expression') return null;
|
|
232
|
+
const innerField = innerFn.childForFieldName('field');
|
|
233
|
+
if (!innerField) return null;
|
|
234
|
+
const ctor = innerField.text;
|
|
235
|
+
// Match `New(.*)(?:Service)?Client$`. Service is the middle capture if it ends
|
|
236
|
+
// in "ServiceClient"; otherwise strip just "Client".
|
|
237
|
+
const m = ctor.match(/^New([A-Z][A-Za-z0-9_]*?)(Service)?Client$/);
|
|
238
|
+
if (!m) return null;
|
|
239
|
+
const serviceName = m[1] + (m[2] ? 'Service' : '');
|
|
240
|
+
const operation = `${serviceName}/${rpcMethod}`;
|
|
241
|
+
return {
|
|
242
|
+
protocol: 'grpc',
|
|
243
|
+
method: rpcMethod.toUpperCase(),
|
|
244
|
+
rawTarget: `${ctor}.${rpcMethod}`,
|
|
245
|
+
framework: 'grpc-go',
|
|
246
|
+
line: outer.startPosition.row,
|
|
247
|
+
confidence: 0.9,
|
|
248
|
+
operation,
|
|
249
|
+
service: serviceName,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function goLooksLikeHttpTarget(s: string): boolean {
|
|
254
|
+
if (!s) return false;
|
|
255
|
+
if (s.startsWith('/')) return true;
|
|
256
|
+
if (/^https?:\/\//i.test(s)) return true;
|
|
257
|
+
if (/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9_-]/.test(s)) return true;
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import type Parser from 'web-tree-sitter';
|
|
2
|
+
import type { SymbolDef, RouteDef, ConfigKeyRead, ServiceCallDef } from '../../types.js';
|
|
3
|
+
import type { LanguageExtractor } from '../walker.js';
|
|
4
|
+
import { firstLine } from '../walker.js';
|
|
5
|
+
|
|
6
|
+
const JAVA_BRANCH_NODES = new Set<string>([
|
|
7
|
+
'if_statement', 'while_statement', 'do_statement', 'for_statement', 'enhanced_for_statement',
|
|
8
|
+
'switch_label', 'catch_clause', 'ternary_expression',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const JAVA_NESTING_NODES = new Set<string>([
|
|
12
|
+
'if_statement', 'while_statement', 'do_statement', 'for_statement', 'enhanced_for_statement',
|
|
13
|
+
'switch_block', 'catch_clause',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const SPRING_REQUEST_ANNOTATIONS: Record<string, string> = {
|
|
17
|
+
GetMapping: 'GET',
|
|
18
|
+
PostMapping: 'POST',
|
|
19
|
+
PutMapping: 'PUT',
|
|
20
|
+
PatchMapping: 'PATCH',
|
|
21
|
+
DeleteMapping: 'DELETE',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Java HTTP CLIENT method names → HTTP verb. RestTemplate exposes a separate
|
|
25
|
+
// method per verb; java.net.http builds the request via .GET()/.POST(…).
|
|
26
|
+
const JAVA_HTTP_CLIENT_METHODS = new Map<string, string>([
|
|
27
|
+
['getForObject', 'GET'], ['getForEntity', 'GET'],
|
|
28
|
+
['postForObject', 'POST'], ['postForEntity', 'POST'], ['postForLocation', 'POST'],
|
|
29
|
+
['put', 'PUT'], ['delete', 'DELETE'],
|
|
30
|
+
['exchange', 'ANY'],
|
|
31
|
+
['newBuilder', 'ANY'], // HttpRequest.newBuilder(URI.create("..."))
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
function javaLooksLikeHttpTarget(s: string): boolean {
|
|
35
|
+
if (!s) return false;
|
|
36
|
+
if (s.startsWith('/')) return true;
|
|
37
|
+
if (/^https?:\/\//i.test(s)) return true;
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const JAVA_CANDIDATE_NODE_TYPES = [
|
|
42
|
+
// tryExtractDefinition
|
|
43
|
+
'method_declaration',
|
|
44
|
+
'class_declaration',
|
|
45
|
+
'interface_declaration',
|
|
46
|
+
'constructor_declaration',
|
|
47
|
+
'enum_declaration',
|
|
48
|
+
// tryExtractCallName + tryExtractConfigKey
|
|
49
|
+
'method_invocation',
|
|
50
|
+
// tryExtractImport
|
|
51
|
+
'import_declaration',
|
|
52
|
+
// tryExtractRoute (Spring annotations on methods)
|
|
53
|
+
'annotation',
|
|
54
|
+
'marker_annotation',
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
export const javaExtractor: LanguageExtractor = {
|
|
58
|
+
languageName: 'java',
|
|
59
|
+
extensions: ['.java'],
|
|
60
|
+
branchNodeTypes: JAVA_BRANCH_NODES,
|
|
61
|
+
nestingNodeTypes: JAVA_NESTING_NODES,
|
|
62
|
+
candidateNodeTypes: JAVA_CANDIDATE_NODE_TYPES,
|
|
63
|
+
|
|
64
|
+
tryExtractDefinition(node: Parser.SyntaxNode): SymbolDef | null {
|
|
65
|
+
switch (node.type) {
|
|
66
|
+
case 'method_declaration': {
|
|
67
|
+
const nameNode = node.childForFieldName('name');
|
|
68
|
+
if (!nameNode) return null;
|
|
69
|
+
return {
|
|
70
|
+
name: nameNode.text,
|
|
71
|
+
kind: 'method',
|
|
72
|
+
lineStart: node.startPosition.row,
|
|
73
|
+
lineEnd: node.endPosition.row,
|
|
74
|
+
colStart: node.startPosition.column,
|
|
75
|
+
colEnd: node.endPosition.column,
|
|
76
|
+
signature: firstLine(node),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
case 'class_declaration': {
|
|
81
|
+
const nameNode = node.childForFieldName('name');
|
|
82
|
+
if (!nameNode) return null;
|
|
83
|
+
return {
|
|
84
|
+
name: nameNode.text,
|
|
85
|
+
kind: 'class',
|
|
86
|
+
lineStart: node.startPosition.row,
|
|
87
|
+
lineEnd: node.endPosition.row,
|
|
88
|
+
colStart: node.startPosition.column,
|
|
89
|
+
colEnd: node.endPosition.column,
|
|
90
|
+
signature: firstLine(node),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case 'interface_declaration': {
|
|
95
|
+
const nameNode = node.childForFieldName('name');
|
|
96
|
+
if (!nameNode) return null;
|
|
97
|
+
return {
|
|
98
|
+
name: nameNode.text,
|
|
99
|
+
kind: 'interface',
|
|
100
|
+
lineStart: node.startPosition.row,
|
|
101
|
+
lineEnd: node.endPosition.row,
|
|
102
|
+
colStart: node.startPosition.column,
|
|
103
|
+
colEnd: node.endPosition.column,
|
|
104
|
+
signature: firstLine(node),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
case 'constructor_declaration': {
|
|
109
|
+
const nameNode = node.childForFieldName('name');
|
|
110
|
+
if (!nameNode) return null;
|
|
111
|
+
return {
|
|
112
|
+
name: nameNode.text,
|
|
113
|
+
kind: 'constructor',
|
|
114
|
+
lineStart: node.startPosition.row,
|
|
115
|
+
lineEnd: node.endPosition.row,
|
|
116
|
+
colStart: node.startPosition.column,
|
|
117
|
+
colEnd: node.endPosition.column,
|
|
118
|
+
signature: firstLine(node),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
case 'enum_declaration': {
|
|
123
|
+
const nameNode = node.childForFieldName('name');
|
|
124
|
+
if (!nameNode) return null;
|
|
125
|
+
return {
|
|
126
|
+
name: nameNode.text,
|
|
127
|
+
kind: 'enum',
|
|
128
|
+
lineStart: node.startPosition.row,
|
|
129
|
+
lineEnd: node.endPosition.row,
|
|
130
|
+
colStart: node.startPosition.column,
|
|
131
|
+
colEnd: node.endPosition.column,
|
|
132
|
+
signature: firstLine(node),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
default:
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
tryExtractCallName(node: Parser.SyntaxNode): string | null {
|
|
142
|
+
if (node.type === 'method_invocation') {
|
|
143
|
+
return node.childForFieldName('name')?.text ?? null;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
tryExtractImport(node: Parser.SyntaxNode): string | null {
|
|
149
|
+
if (node.type === 'import_declaration') {
|
|
150
|
+
for (const child of node.children) {
|
|
151
|
+
if (child.type === 'scoped_identifier' || child.type === 'identifier') {
|
|
152
|
+
return child.text;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Spring Boot mapping annotations on methods:
|
|
161
|
+
* @GetMapping("/users")
|
|
162
|
+
* @PostMapping(value = "/users")
|
|
163
|
+
* @RequestMapping(value = "/x", method = RequestMethod.GET)
|
|
164
|
+
*
|
|
165
|
+
* Class-level @RequestMapping("/api") is treated as a PREFIX for every
|
|
166
|
+
* mapping annotation on that class's methods, not as a route on its own.
|
|
167
|
+
* We never emit a route from a class-level annotation directly — that
|
|
168
|
+
* was a pre-existing bug that produced bogus entries like `GET /api`.
|
|
169
|
+
*
|
|
170
|
+
* The annotation node sits above the method_declaration; we extract on
|
|
171
|
+
* the annotation and walk up to the method for the handler name, then
|
|
172
|
+
* walk further up to find the enclosing class to pick up any class-level
|
|
173
|
+
* @RequestMapping prefix.
|
|
174
|
+
*/
|
|
175
|
+
tryExtractRoute(node: Parser.SyntaxNode): RouteDef[] | null {
|
|
176
|
+
if (node.type !== 'annotation' && node.type !== 'marker_annotation') return null;
|
|
177
|
+
const nameNode = node.childForFieldName('name');
|
|
178
|
+
if (!nameNode) return null;
|
|
179
|
+
const annName = nameNode.text;
|
|
180
|
+
const method = SPRING_REQUEST_ANNOTATIONS[annName];
|
|
181
|
+
|
|
182
|
+
// The enclosing declaration (method vs class) decides whether this
|
|
183
|
+
// annotation is a route or a prefix. modifiers are the syntactic parent
|
|
184
|
+
// of an annotation in tree-sitter-java; the method/class is one more
|
|
185
|
+
// level up.
|
|
186
|
+
let enclosing: Parser.SyntaxNode | null = node.parent;
|
|
187
|
+
while (enclosing && enclosing.type === 'modifiers') enclosing = enclosing.parent;
|
|
188
|
+
if (!enclosing) return null;
|
|
189
|
+
// Class-level annotations: not a route on their own. Their path becomes
|
|
190
|
+
// a prefix for method-level routes inside the class; the method-level
|
|
191
|
+
// pass below walks up to find this annotation and prepends its path.
|
|
192
|
+
if (enclosing.type === 'class_declaration' || enclosing.type === 'interface_declaration') {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
// Anything other than a method/constructor: not a route.
|
|
196
|
+
if (enclosing.type !== 'method_declaration' && enclosing.type !== 'constructor_declaration') {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const routePath = readSpringPath(node);
|
|
201
|
+
|
|
202
|
+
let detectedMethod = method;
|
|
203
|
+
const args = node.childForFieldName('arguments');
|
|
204
|
+
if (annName === 'RequestMapping' && args) {
|
|
205
|
+
for (const child of args.namedChildren) {
|
|
206
|
+
if (child.type === 'element_value_pair') {
|
|
207
|
+
const kn = child.childForFieldName('key');
|
|
208
|
+
const vn = child.childForFieldName('value');
|
|
209
|
+
if (kn?.text === 'method' && vn) {
|
|
210
|
+
const txt = vn.text.replace(/.*RequestMethod\./, '').replace(/[,}\s]/g, '');
|
|
211
|
+
if (txt) detectedMethod = txt;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!detectedMethod) detectedMethod = 'GET';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!detectedMethod) return null;
|
|
219
|
+
|
|
220
|
+
// Walk up to the enclosing class to pick up a class-level @RequestMapping
|
|
221
|
+
// prefix (if any). Concatenate with care so we don't end up with double
|
|
222
|
+
// slashes or missing slashes.
|
|
223
|
+
const prefix = findSpringClassPrefix(enclosing);
|
|
224
|
+
const fullPath = joinSpringPaths(prefix, routePath);
|
|
225
|
+
if (!fullPath) return null;
|
|
226
|
+
|
|
227
|
+
const handlerName = enclosing.childForFieldName('name')?.text;
|
|
228
|
+
|
|
229
|
+
return [{
|
|
230
|
+
method: detectedMethod,
|
|
231
|
+
path: fullPath,
|
|
232
|
+
framework: 'spring',
|
|
233
|
+
handlerName,
|
|
234
|
+
line: node.startPosition.row,
|
|
235
|
+
}];
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Java HTTP client calls — minimal initial coverage:
|
|
240
|
+
* HttpRequest.newBuilder(URI.create("https://x/y"))
|
|
241
|
+
* .GET().build() ← yes (path lifted; method=GET)
|
|
242
|
+
* restTemplate.getForObject("/api/x", String.class) ← yes (Spring RestTemplate)
|
|
243
|
+
* webClient.get().uri("/api/x") ← future
|
|
244
|
+
*
|
|
245
|
+
* The recognizer is conservative: we accept any method_invocation whose
|
|
246
|
+
* name is in JAVA_HTTP_CLIENT_METHODS and whose first argument is a string
|
|
247
|
+
* literal that looks like an HTTP target.
|
|
248
|
+
*/
|
|
249
|
+
tryExtractServiceCalls(node: Parser.SyntaxNode): ServiceCallDef[] | null {
|
|
250
|
+
if (node.type !== 'method_invocation') return null;
|
|
251
|
+
const name = node.childForFieldName('name')?.text;
|
|
252
|
+
if (!name) return null;
|
|
253
|
+
const verb = JAVA_HTTP_CLIENT_METHODS.get(name);
|
|
254
|
+
if (!verb) return null;
|
|
255
|
+
|
|
256
|
+
const args = node.childForFieldName('arguments');
|
|
257
|
+
if (!args) return null;
|
|
258
|
+
const first = args.namedChildren[0];
|
|
259
|
+
if (!first) return null;
|
|
260
|
+
let raw: string | null = null;
|
|
261
|
+
if (first.type === 'string_literal') {
|
|
262
|
+
raw = stripJavaQuotes(first.text);
|
|
263
|
+
} else if (first.type === 'method_invocation') {
|
|
264
|
+
// URI.create("https://x/y") inside HttpRequest.newBuilder(...)
|
|
265
|
+
const innerName = first.childForFieldName('name')?.text;
|
|
266
|
+
const innerObj = first.childForFieldName('object')?.text;
|
|
267
|
+
if (innerObj === 'URI' && innerName === 'create') {
|
|
268
|
+
const ia = first.childForFieldName('arguments');
|
|
269
|
+
const first2 = ia?.namedChildren[0];
|
|
270
|
+
if (first2?.type === 'string_literal') raw = stripJavaQuotes(first2.text);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (!raw || !javaLooksLikeHttpTarget(raw)) return null;
|
|
274
|
+
|
|
275
|
+
let framework = 'http-client';
|
|
276
|
+
const obj = node.childForFieldName('object')?.text;
|
|
277
|
+
if (obj === 'HttpRequest') framework = 'java.net.http';
|
|
278
|
+
else if (obj && /(restTemplate|RestTemplate)/.test(obj)) framework = 'spring-rest';
|
|
279
|
+
|
|
280
|
+
return [{
|
|
281
|
+
protocol: 'http',
|
|
282
|
+
method: verb,
|
|
283
|
+
rawTarget: raw.slice(0, 240),
|
|
284
|
+
framework,
|
|
285
|
+
line: node.startPosition.row,
|
|
286
|
+
confidence: 0.8,
|
|
287
|
+
}];
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Java env reads: `System.getenv("NAME")`.
|
|
292
|
+
*/
|
|
293
|
+
tryExtractConfigKey(node: Parser.SyntaxNode): ConfigKeyRead | null {
|
|
294
|
+
if (node.type !== 'method_invocation') return null;
|
|
295
|
+
const obj = node.childForFieldName('object');
|
|
296
|
+
const name = node.childForFieldName('name');
|
|
297
|
+
if (!obj || !name) return null;
|
|
298
|
+
if (obj.text === 'System' && name.text === 'getenv') {
|
|
299
|
+
const args = node.childForFieldName('arguments');
|
|
300
|
+
if (!args) return null;
|
|
301
|
+
for (const child of args.namedChildren) {
|
|
302
|
+
if (child.type === 'string_literal') {
|
|
303
|
+
const key = stripJavaQuotes(child.text);
|
|
304
|
+
if (key) return { key, source: 'env', line: node.startPosition.row };
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
function stripJavaQuotes(s: string): string {
|
|
314
|
+
return s.replace(/^"|"$/g, '');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Read the `value=`/`path=` (or first positional string-literal) from a
|
|
319
|
+
* Spring mapping annotation. Returns '' when no path is given — which is
|
|
320
|
+
* a perfectly valid mapping (`@GetMapping` with class-level prefix only).
|
|
321
|
+
*/
|
|
322
|
+
function readSpringPath(annNode: Parser.SyntaxNode): string {
|
|
323
|
+
const args = annNode.childForFieldName('arguments');
|
|
324
|
+
if (!args) return '';
|
|
325
|
+
for (const child of args.namedChildren) {
|
|
326
|
+
if (child.type === 'string_literal') return stripJavaQuotes(child.text);
|
|
327
|
+
if (child.type === 'element_value_pair') {
|
|
328
|
+
const kn = child.childForFieldName('key');
|
|
329
|
+
const vn = child.childForFieldName('value');
|
|
330
|
+
if ((kn?.text === 'value' || kn?.text === 'path') && vn?.type === 'string_literal') {
|
|
331
|
+
return stripJavaQuotes(vn.text);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return '';
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Walk up from a method_declaration to its enclosing class_declaration and
|
|
340
|
+
* return the path component of a class-level @RequestMapping(...) — or '' if
|
|
341
|
+
* the class has no such annotation. Bare class-level @RestController without
|
|
342
|
+
* a @RequestMapping yields ''.
|
|
343
|
+
*/
|
|
344
|
+
function findSpringClassPrefix(methodOrCtor: Parser.SyntaxNode): string {
|
|
345
|
+
let n: Parser.SyntaxNode | null = methodOrCtor.parent;
|
|
346
|
+
while (n) {
|
|
347
|
+
if (n.type === 'class_declaration' || n.type === 'interface_declaration') break;
|
|
348
|
+
n = n.parent;
|
|
349
|
+
}
|
|
350
|
+
if (!n) return '';
|
|
351
|
+
// Class-level annotations sit in a `modifiers` block as the first child of
|
|
352
|
+
// the class_declaration in tree-sitter-java.
|
|
353
|
+
for (const child of n.children) {
|
|
354
|
+
if (child.type !== 'modifiers') continue;
|
|
355
|
+
for (const m of child.namedChildren) {
|
|
356
|
+
if (m.type !== 'annotation' && m.type !== 'marker_annotation') continue;
|
|
357
|
+
const nm = m.childForFieldName('name');
|
|
358
|
+
if (nm?.text !== 'RequestMapping') continue;
|
|
359
|
+
return readSpringPath(m);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return '';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Join a Spring class-level prefix and method-level path. Empty strings are
|
|
367
|
+
* dropped; consecutive slashes are collapsed. Returns '' when both inputs
|
|
368
|
+
* are empty.
|
|
369
|
+
* ('/api', '/users') → '/api/users'
|
|
370
|
+
* ('/api', '') → '/api'
|
|
371
|
+
* ('', '/users') → '/users'
|
|
372
|
+
* ('/api/', '/users') → '/api/users'
|
|
373
|
+
* ('', '') → ''
|
|
374
|
+
*/
|
|
375
|
+
function joinSpringPaths(prefix: string, route: string): string {
|
|
376
|
+
if (!prefix && !route) return '';
|
|
377
|
+
if (!prefix) return route;
|
|
378
|
+
if (!route) return prefix;
|
|
379
|
+
const a = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
|
380
|
+
const b = route.startsWith('/') ? route : '/' + route;
|
|
381
|
+
return a + b;
|
|
382
|
+
}
|