gitnexus 1.6.0 → 1.6.2-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/dist/cli/analyze.js +50 -3
- package/dist/core/group/extractors/fs-utils.d.ts +10 -0
- package/dist/core/group/extractors/fs-utils.js +24 -0
- package/dist/core/group/extractors/grpc-extractor.d.ts +17 -8
- package/dist/core/group/extractors/grpc-extractor.js +328 -191
- package/dist/core/group/extractors/grpc-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/go.js +97 -0
- package/dist/core/group/extractors/grpc-patterns/index.d.ts +19 -0
- package/dist/core/group/extractors/grpc-patterns/index.js +46 -0
- package/dist/core/group/extractors/grpc-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/java.js +173 -0
- package/dist/core/group/extractors/grpc-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/grpc-patterns/node.js +290 -0
- package/dist/core/group/extractors/grpc-patterns/proto.d.ts +9 -0
- package/dist/core/group/extractors/grpc-patterns/proto.js +134 -0
- package/dist/core/group/extractors/grpc-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/python.js +67 -0
- package/dist/core/group/extractors/grpc-patterns/types.d.ts +50 -0
- package/dist/core/group/extractors/grpc-patterns/types.js +1 -0
- package/dist/core/group/extractors/http-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/go.js +215 -0
- package/dist/core/group/extractors/http-patterns/index.d.ts +17 -0
- package/dist/core/group/extractors/http-patterns/index.js +44 -0
- package/dist/core/group/extractors/http-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/java.js +253 -0
- package/dist/core/group/extractors/http-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/http-patterns/node.js +354 -0
- package/dist/core/group/extractors/http-patterns/php.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/php.js +70 -0
- package/dist/core/group/extractors/http-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/python.js +133 -0
- package/dist/core/group/extractors/http-patterns/types.d.ts +61 -0
- package/dist/core/group/extractors/http-patterns/types.js +1 -0
- package/dist/core/group/extractors/http-route-extractor.d.ts +10 -13
- package/dist/core/group/extractors/http-route-extractor.js +231 -238
- package/dist/core/group/extractors/manifest-extractor.d.ts +54 -0
- package/dist/core/group/extractors/manifest-extractor.js +277 -0
- package/dist/core/group/extractors/topic-extractor.d.ts +0 -1
- package/dist/core/group/extractors/topic-extractor.js +55 -192
- package/dist/core/group/extractors/topic-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/go.js +120 -0
- package/dist/core/group/extractors/topic-patterns/index.d.ts +14 -0
- package/dist/core/group/extractors/topic-patterns/index.js +38 -0
- package/dist/core/group/extractors/topic-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/java.js +80 -0
- package/dist/core/group/extractors/topic-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/topic-patterns/node.js +155 -0
- package/dist/core/group/extractors/topic-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/python.js +116 -0
- package/dist/core/group/extractors/topic-patterns/types.d.ts +25 -0
- package/dist/core/group/extractors/topic-patterns/types.js +10 -0
- package/dist/core/group/extractors/tree-sitter-scanner.d.ts +113 -0
- package/dist/core/group/extractors/tree-sitter-scanner.js +94 -0
- package/dist/core/ingestion/binding-accumulator.d.ts +22 -17
- package/dist/core/ingestion/binding-accumulator.js +29 -25
- package/dist/core/ingestion/cobol-processor.d.ts +1 -1
- package/dist/core/ingestion/import-processor.js +1 -1
- package/dist/core/ingestion/language-config.js +1 -1
- package/dist/core/ingestion/language-provider.d.ts +32 -5
- package/dist/core/ingestion/languages/c-cpp.js +2 -2
- package/dist/core/ingestion/languages/dart.d.ts +1 -1
- package/dist/core/ingestion/languages/dart.js +2 -2
- package/dist/core/ingestion/languages/go.d.ts +1 -1
- package/dist/core/ingestion/languages/go.js +2 -2
- package/dist/core/ingestion/languages/ruby.js +16 -1
- package/dist/core/ingestion/languages/swift.d.ts +1 -1
- package/dist/core/ingestion/languages/swift.js +2 -2
- package/dist/core/ingestion/markdown-processor.d.ts +1 -1
- package/dist/core/ingestion/method-extractors/configs/jvm.js +1 -0
- package/dist/core/ingestion/method-extractors/configs/ruby.js +1 -0
- package/dist/core/ingestion/method-extractors/generic.d.ts +6 -0
- package/dist/core/ingestion/method-extractors/generic.js +48 -4
- package/dist/core/ingestion/method-types.d.ts +4 -0
- package/dist/core/ingestion/model/resolve.js +103 -48
- package/dist/core/ingestion/model/semantic-model.d.ts +1 -1
- package/dist/core/ingestion/model/semantic-model.js +1 -1
- package/dist/core/ingestion/model/symbol-table.d.ts +7 -7
- package/dist/core/ingestion/model/symbol-table.js +7 -7
- package/dist/core/ingestion/mro-processor.d.ts +1 -1
- package/dist/core/ingestion/mro-processor.js +1 -1
- package/dist/core/ingestion/parsing-processor.js +54 -42
- package/dist/core/ingestion/pipeline-phases/cobol.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/cobol.js +45 -0
- package/dist/core/ingestion/pipeline-phases/communities.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/communities.js +62 -0
- package/dist/core/ingestion/pipeline-phases/cross-file-impl.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +156 -0
- package/dist/core/ingestion/pipeline-phases/cross-file.d.ts +37 -0
- package/dist/core/ingestion/pipeline-phases/cross-file.js +63 -0
- package/dist/core/ingestion/pipeline-phases/index.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/index.js +22 -0
- package/dist/core/ingestion/pipeline-phases/markdown.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/markdown.js +33 -0
- package/dist/core/ingestion/pipeline-phases/mro.d.ts +18 -0
- package/dist/core/ingestion/pipeline-phases/mro.js +36 -0
- package/dist/core/ingestion/pipeline-phases/orm-extraction.d.ts +22 -0
- package/dist/core/ingestion/pipeline-phases/orm-extraction.js +92 -0
- package/dist/core/ingestion/pipeline-phases/orm.d.ts +15 -0
- package/dist/core/ingestion/pipeline-phases/orm.js +74 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +47 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +437 -0
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +49 -0
- package/dist/core/ingestion/pipeline-phases/parse.js +33 -0
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/processes.js +143 -0
- package/dist/core/ingestion/pipeline-phases/routes.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/routes.js +243 -0
- package/dist/core/ingestion/pipeline-phases/runner.d.ts +22 -0
- package/dist/core/ingestion/pipeline-phases/runner.js +203 -0
- package/dist/core/ingestion/pipeline-phases/scan.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/scan.js +46 -0
- package/dist/core/ingestion/pipeline-phases/structure.d.ts +27 -0
- package/dist/core/ingestion/pipeline-phases/structure.js +35 -0
- package/dist/core/ingestion/pipeline-phases/tools.d.ts +20 -0
- package/dist/core/ingestion/pipeline-phases/tools.js +79 -0
- package/dist/core/ingestion/pipeline-phases/types.d.ts +79 -0
- package/dist/core/ingestion/pipeline-phases/types.js +37 -0
- package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.d.ts +70 -0
- package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.js +312 -0
- package/dist/core/ingestion/pipeline.d.ts +16 -10
- package/dist/core/ingestion/pipeline.js +66 -1534
- package/dist/core/ingestion/process-processor.js +1 -1
- package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
- package/dist/core/ingestion/tree-sitter-queries.js +69 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +1 -3
- package/dist/core/ingestion/utils/ast-helpers.js +48 -21
- package/dist/core/ingestion/utils/env.d.ts +10 -0
- package/dist/core/ingestion/utils/env.js +10 -0
- package/dist/core/ingestion/utils/graph-sort.d.ts +58 -0
- package/dist/core/ingestion/utils/graph-sort.js +100 -0
- package/dist/core/ingestion/workers/parse-worker.js +12 -8
- package/dist/core/lbug/lbug-adapter.d.ts +28 -0
- package/dist/core/lbug/lbug-adapter.js +162 -57
- package/package.json +3 -3
- package/vendor/tree-sitter-proto/binding.gyp +30 -0
- package/vendor/tree-sitter-proto/bindings/node/binding.cc +20 -0
- package/vendor/tree-sitter-proto/bindings/node/index.d.ts +28 -0
- package/vendor/tree-sitter-proto/bindings/node/index.js +7 -0
- package/vendor/tree-sitter-proto/package.json +18 -0
- package/vendor/tree-sitter-proto/src/node-types.json +1145 -0
- package/vendor/tree-sitter-proto/src/parser.c +10149 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/alloc.h +54 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/array.h +291 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/parser.h +266 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import Python from 'tree-sitter-python';
|
|
2
|
+
import { compilePatterns, runCompiledPatterns, } from '../tree-sitter-scanner.js';
|
|
3
|
+
/**
|
|
4
|
+
* Python gRPC plugin. Detects:
|
|
5
|
+
* - Provider: `add_XxxServicer_to_server(...)` calls (bare identifier
|
|
6
|
+
* or qualified attribute form `auth_pb2_grpc.add_XxxServicer_to_server`)
|
|
7
|
+
* - Consumer: `XxxStub(channel)` calls (bare or `auth_pb2_grpc.XxxStub`)
|
|
8
|
+
*/
|
|
9
|
+
const ADD_SERVICER_RE = /^add_(\w+)Servicer_to_server$/;
|
|
10
|
+
const STUB_RE = /^(\w+)Stub$/;
|
|
11
|
+
/** Reserved names that would produce garbage service names. */
|
|
12
|
+
const STUB_IGNORE = new Set(['Mock', 'Test', 'Fake', 'Stub']);
|
|
13
|
+
// Any call whose target is either a bare identifier or an attribute
|
|
14
|
+
// access (`obj.method`). The plugin filters the function name in JS.
|
|
15
|
+
const CALL_PATTERNS = compilePatterns({
|
|
16
|
+
name: 'python-grpc-call',
|
|
17
|
+
language: Python,
|
|
18
|
+
patterns: [
|
|
19
|
+
{
|
|
20
|
+
meta: {},
|
|
21
|
+
query: `
|
|
22
|
+
(call
|
|
23
|
+
function: [
|
|
24
|
+
(identifier) @fn
|
|
25
|
+
(attribute attribute: (identifier) @fn)
|
|
26
|
+
])
|
|
27
|
+
`,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
export const PYTHON_GRPC_PLUGIN = {
|
|
32
|
+
name: 'python-grpc',
|
|
33
|
+
language: Python,
|
|
34
|
+
scan(tree) {
|
|
35
|
+
const out = [];
|
|
36
|
+
for (const match of runCompiledPatterns(CALL_PATTERNS, tree)) {
|
|
37
|
+
const fnNode = match.captures.fn;
|
|
38
|
+
if (!fnNode)
|
|
39
|
+
continue;
|
|
40
|
+
const fnText = fnNode.text;
|
|
41
|
+
const addServicer = ADD_SERVICER_RE.exec(fnText);
|
|
42
|
+
if (addServicer) {
|
|
43
|
+
out.push({
|
|
44
|
+
role: 'provider',
|
|
45
|
+
serviceName: addServicer[1],
|
|
46
|
+
symbolName: fnText,
|
|
47
|
+
source: 'python_servicer',
|
|
48
|
+
confidenceWithProto: 0.8,
|
|
49
|
+
confidenceWithoutProto: 0.65,
|
|
50
|
+
});
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const stubMatch = STUB_RE.exec(fnText);
|
|
54
|
+
if (stubMatch && !STUB_IGNORE.has(stubMatch[1])) {
|
|
55
|
+
out.push({
|
|
56
|
+
role: 'consumer',
|
|
57
|
+
serviceName: stubMatch[1],
|
|
58
|
+
symbolName: fnText,
|
|
59
|
+
source: 'python_stub',
|
|
60
|
+
confidenceWithProto: 0.75,
|
|
61
|
+
confidenceWithoutProto: 0.55,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type Parser from 'tree-sitter';
|
|
2
|
+
/**
|
|
3
|
+
* Shared types for the grpc-extractor language plugins.
|
|
4
|
+
*
|
|
5
|
+
* Each plugin lives in its own file (java.ts, go.ts, ...) and owns the
|
|
6
|
+
* tree-sitter grammar import + query sources. The top-level
|
|
7
|
+
* `grpc-extractor.ts` orchestrator only knows about this type module
|
|
8
|
+
* and the plugin registry (`./index.ts`). It MUST NOT import any
|
|
9
|
+
* grammar or query text directly.
|
|
10
|
+
*/
|
|
11
|
+
export type GrpcRole = 'provider' | 'consumer';
|
|
12
|
+
/**
|
|
13
|
+
* One raw gRPC detection produced by a plugin's `scan()` function. The
|
|
14
|
+
* orchestrator uses the proto map to resolve the full package-qualified
|
|
15
|
+
* contract id and choose a confidence based on whether the proto was
|
|
16
|
+
* found.
|
|
17
|
+
*
|
|
18
|
+
* Most patterns produce service-level detections; `TS @GrpcMethod` is
|
|
19
|
+
* the only pattern that captures an explicit `methodName`, producing
|
|
20
|
+
* a method-level contract (`grpc::pkg.Service/Method`).
|
|
21
|
+
*/
|
|
22
|
+
export interface GrpcDetection {
|
|
23
|
+
role: GrpcRole;
|
|
24
|
+
/** Short service name, e.g. `"AuthService"`. */
|
|
25
|
+
serviceName: string;
|
|
26
|
+
/** Symbol name emitted into the contract's symbolRef. */
|
|
27
|
+
symbolName: string;
|
|
28
|
+
/** Metadata source label (goes into `meta.source`). */
|
|
29
|
+
source: string;
|
|
30
|
+
/** Explicit method name; set only by TS `@GrpcMethod`. */
|
|
31
|
+
methodName?: string;
|
|
32
|
+
/** Confidence when the proto map resolves the service. */
|
|
33
|
+
confidenceWithProto: number;
|
|
34
|
+
/** Confidence when the proto map has no entry. */
|
|
35
|
+
confidenceWithoutProto: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* One language-scoped gRPC plugin. Plugins own the tree-sitter grammar
|
|
39
|
+
* and a `scan(tree)` function that returns zero or more
|
|
40
|
+
* `GrpcDetection`s. The plugin is free to run multiple compiled query
|
|
41
|
+
* bundles and walk the AST to cross-reference captures.
|
|
42
|
+
*
|
|
43
|
+
* `language` is typed `unknown` for the same reason as in
|
|
44
|
+
* `tree-sitter-scanner.ts`.
|
|
45
|
+
*/
|
|
46
|
+
export interface GrpcLanguagePlugin {
|
|
47
|
+
name: string;
|
|
48
|
+
language: unknown;
|
|
49
|
+
scan(tree: Parser.Tree): GrpcDetection[];
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import Go from 'tree-sitter-go';
|
|
2
|
+
import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
|
|
3
|
+
/**
|
|
4
|
+
* Go HTTP plugin. Handles:
|
|
5
|
+
* - gin / echo / chi framework routing — `r.GET("/path", handler)`
|
|
6
|
+
* - net/http stdlib — `http.HandleFunc("/path", handler)`
|
|
7
|
+
* - net/http consumer — `http.Get(...)`, `http.NewRequest("METHOD", ...)`
|
|
8
|
+
* - resty consumer — `client.R().Delete("/path")`
|
|
9
|
+
*/
|
|
10
|
+
// ─── Provider: framework routing ──────────────────────────────────────
|
|
11
|
+
// Matches `\w+\.GET(...)` etc. (gin, echo, chi all share this shape).
|
|
12
|
+
// Captures the HTTP method (field name), path literal, and handler
|
|
13
|
+
// identifier passed as the second argument.
|
|
14
|
+
const FRAMEWORK_ROUTE_PATTERNS = compilePatterns({
|
|
15
|
+
name: 'go-framework-route',
|
|
16
|
+
language: Go,
|
|
17
|
+
patterns: [
|
|
18
|
+
{
|
|
19
|
+
meta: {},
|
|
20
|
+
query: `
|
|
21
|
+
(call_expression
|
|
22
|
+
function: (selector_expression
|
|
23
|
+
field: (field_identifier) @http_method (#match? @http_method "^(GET|POST|PUT|DELETE|PATCH)$"))
|
|
24
|
+
arguments: (argument_list
|
|
25
|
+
(interpreted_string_literal) @path
|
|
26
|
+
(identifier) @handler))
|
|
27
|
+
`,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
// ─── Provider: net/http `http.HandleFunc("/p", handler)` ─────────────
|
|
32
|
+
const HANDLE_FUNC_PATTERNS = compilePatterns({
|
|
33
|
+
name: 'go-handle-func',
|
|
34
|
+
language: Go,
|
|
35
|
+
patterns: [
|
|
36
|
+
{
|
|
37
|
+
meta: {},
|
|
38
|
+
query: `
|
|
39
|
+
(call_expression
|
|
40
|
+
function: (selector_expression
|
|
41
|
+
operand: (identifier) @pkg (#eq? @pkg "http")
|
|
42
|
+
field: (field_identifier) @fn (#eq? @fn "HandleFunc"))
|
|
43
|
+
arguments: (argument_list
|
|
44
|
+
(interpreted_string_literal) @path
|
|
45
|
+
(identifier) @handler))
|
|
46
|
+
`,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
// ─── Consumer: net/http stdlib Get / Post / Head ─────────────────────
|
|
51
|
+
const HTTP_CLIENT_METHOD_TO_HTTP = {
|
|
52
|
+
Get: 'GET',
|
|
53
|
+
Post: 'POST',
|
|
54
|
+
Head: 'GET', // HEAD has no body semantics we care about — treat as GET for contract matching
|
|
55
|
+
};
|
|
56
|
+
const HTTP_CLIENT_PATTERNS = compilePatterns({
|
|
57
|
+
name: 'go-http-client',
|
|
58
|
+
language: Go,
|
|
59
|
+
patterns: [
|
|
60
|
+
{
|
|
61
|
+
meta: {},
|
|
62
|
+
query: `
|
|
63
|
+
(call_expression
|
|
64
|
+
function: (selector_expression
|
|
65
|
+
operand: (identifier) @pkg (#eq? @pkg "http")
|
|
66
|
+
field: (field_identifier) @fn (#match? @fn "^(Get|Post|Head)$"))
|
|
67
|
+
arguments: (argument_list . (interpreted_string_literal) @path))
|
|
68
|
+
`,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
// ─── Consumer: net/http `http.NewRequest("METHOD", "/path", ...)` ────
|
|
73
|
+
const NEW_REQUEST_PATTERNS = compilePatterns({
|
|
74
|
+
name: 'go-new-request',
|
|
75
|
+
language: Go,
|
|
76
|
+
patterns: [
|
|
77
|
+
{
|
|
78
|
+
meta: {},
|
|
79
|
+
query: `
|
|
80
|
+
(call_expression
|
|
81
|
+
function: (selector_expression
|
|
82
|
+
operand: (identifier) @pkg (#eq? @pkg "http")
|
|
83
|
+
field: (field_identifier) @fn (#eq? @fn "NewRequest"))
|
|
84
|
+
arguments: (argument_list
|
|
85
|
+
.
|
|
86
|
+
(interpreted_string_literal) @http_method
|
|
87
|
+
(interpreted_string_literal) @path))
|
|
88
|
+
`,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
// ─── Consumer: resty `client.R().Delete("/path")` ─────────────────────
|
|
93
|
+
// Matches any chained call whose receiver is `something.R()` and whose
|
|
94
|
+
// method name is an HTTP verb. This is how go-resty's fluent API looks.
|
|
95
|
+
const RESTY_PATTERNS = compilePatterns({
|
|
96
|
+
name: 'go-resty',
|
|
97
|
+
language: Go,
|
|
98
|
+
patterns: [
|
|
99
|
+
{
|
|
100
|
+
meta: {},
|
|
101
|
+
query: `
|
|
102
|
+
(call_expression
|
|
103
|
+
function: (selector_expression
|
|
104
|
+
operand: (call_expression
|
|
105
|
+
function: (selector_expression
|
|
106
|
+
field: (field_identifier) @r (#eq? @r "R")))
|
|
107
|
+
field: (field_identifier) @http_method (#match? @http_method "^(Get|Post|Put|Delete|Patch)$"))
|
|
108
|
+
arguments: (argument_list . (interpreted_string_literal) @path))
|
|
109
|
+
`,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
export const GO_HTTP_PLUGIN = {
|
|
114
|
+
name: 'go-http',
|
|
115
|
+
language: Go,
|
|
116
|
+
scan(tree) {
|
|
117
|
+
const out = [];
|
|
118
|
+
// Framework providers: r.GET/POST/... with handler identifier
|
|
119
|
+
for (const match of runCompiledPatterns(FRAMEWORK_ROUTE_PATTERNS, tree)) {
|
|
120
|
+
const methodNode = match.captures.http_method;
|
|
121
|
+
const pathNode = match.captures.path;
|
|
122
|
+
const handlerNode = match.captures.handler;
|
|
123
|
+
if (!methodNode || !pathNode)
|
|
124
|
+
continue;
|
|
125
|
+
const path = unquoteLiteral(pathNode.text);
|
|
126
|
+
if (path === null)
|
|
127
|
+
continue;
|
|
128
|
+
out.push({
|
|
129
|
+
role: 'provider',
|
|
130
|
+
framework: 'go-framework',
|
|
131
|
+
method: methodNode.text.toUpperCase(),
|
|
132
|
+
path,
|
|
133
|
+
name: handlerNode?.text ?? null,
|
|
134
|
+
confidence: 0.8,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// net/http HandleFunc: default method GET
|
|
138
|
+
for (const match of runCompiledPatterns(HANDLE_FUNC_PATTERNS, tree)) {
|
|
139
|
+
const pathNode = match.captures.path;
|
|
140
|
+
const handlerNode = match.captures.handler;
|
|
141
|
+
if (!pathNode)
|
|
142
|
+
continue;
|
|
143
|
+
const path = unquoteLiteral(pathNode.text);
|
|
144
|
+
if (path === null)
|
|
145
|
+
continue;
|
|
146
|
+
out.push({
|
|
147
|
+
role: 'provider',
|
|
148
|
+
framework: 'go-stdlib',
|
|
149
|
+
method: 'GET',
|
|
150
|
+
path,
|
|
151
|
+
name: handlerNode?.text ?? null,
|
|
152
|
+
confidence: 0.8,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// net/http client: http.Get/Post/Head
|
|
156
|
+
for (const match of runCompiledPatterns(HTTP_CLIENT_PATTERNS, tree)) {
|
|
157
|
+
const fnNode = match.captures.fn;
|
|
158
|
+
const pathNode = match.captures.path;
|
|
159
|
+
if (!fnNode || !pathNode)
|
|
160
|
+
continue;
|
|
161
|
+
const httpMethod = HTTP_CLIENT_METHOD_TO_HTTP[fnNode.text];
|
|
162
|
+
if (!httpMethod)
|
|
163
|
+
continue;
|
|
164
|
+
const path = unquoteLiteral(pathNode.text);
|
|
165
|
+
if (path === null)
|
|
166
|
+
continue;
|
|
167
|
+
out.push({
|
|
168
|
+
role: 'consumer',
|
|
169
|
+
framework: 'go-stdlib',
|
|
170
|
+
method: httpMethod,
|
|
171
|
+
path,
|
|
172
|
+
name: null,
|
|
173
|
+
confidence: 0.7,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// net/http NewRequest
|
|
177
|
+
for (const match of runCompiledPatterns(NEW_REQUEST_PATTERNS, tree)) {
|
|
178
|
+
const methodNode = match.captures.http_method;
|
|
179
|
+
const pathNode = match.captures.path;
|
|
180
|
+
if (!methodNode || !pathNode)
|
|
181
|
+
continue;
|
|
182
|
+
const method = unquoteLiteral(methodNode.text);
|
|
183
|
+
const path = unquoteLiteral(pathNode.text);
|
|
184
|
+
if (method === null || path === null)
|
|
185
|
+
continue;
|
|
186
|
+
out.push({
|
|
187
|
+
role: 'consumer',
|
|
188
|
+
framework: 'go-stdlib',
|
|
189
|
+
method: method.toUpperCase(),
|
|
190
|
+
path,
|
|
191
|
+
name: null,
|
|
192
|
+
confidence: 0.7,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
// resty
|
|
196
|
+
for (const match of runCompiledPatterns(RESTY_PATTERNS, tree)) {
|
|
197
|
+
const methodNode = match.captures.http_method;
|
|
198
|
+
const pathNode = match.captures.path;
|
|
199
|
+
if (!methodNode || !pathNode)
|
|
200
|
+
continue;
|
|
201
|
+
const path = unquoteLiteral(pathNode.text);
|
|
202
|
+
if (path === null)
|
|
203
|
+
continue;
|
|
204
|
+
out.push({
|
|
205
|
+
role: 'consumer',
|
|
206
|
+
framework: 'go-resty',
|
|
207
|
+
method: methodNode.text.toUpperCase(),
|
|
208
|
+
path,
|
|
209
|
+
name: null,
|
|
210
|
+
confidence: 0.7,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HttpLanguagePlugin } from './types.js';
|
|
2
|
+
export type { HttpDetection, HttpLanguagePlugin, HttpRole } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Glob for files worth scanning for HTTP routes. Kept alongside the
|
|
5
|
+
* registry so adding a new language widens the glob in one edit.
|
|
6
|
+
*
|
|
7
|
+
* `.vue` / `.svelte` files are intentionally omitted for the source-scan
|
|
8
|
+
* path — they need their own grammar-aware extraction and the existing
|
|
9
|
+
* regex fallback for them was never very accurate. The graph-assisted
|
|
10
|
+
* Strategy A still handles them via the ingestion pipeline.
|
|
11
|
+
*/
|
|
12
|
+
export declare const HTTP_SCAN_GLOB = "**/*.{ts,tsx,js,jsx,java,go,py,php}";
|
|
13
|
+
/**
|
|
14
|
+
* Return the HTTP plugin registered for the given file's extension,
|
|
15
|
+
* or `undefined` if the extension is not registered.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getPluginForFile(rel: string): HttpLanguagePlugin | undefined;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { JAVA_HTTP_PLUGIN } from './java.js';
|
|
3
|
+
import { GO_HTTP_PLUGIN } from './go.js';
|
|
4
|
+
import { PYTHON_HTTP_PLUGIN } from './python.js';
|
|
5
|
+
import { PHP_HTTP_PLUGIN } from './php.js';
|
|
6
|
+
import { JAVASCRIPT_HTTP_PLUGIN, TYPESCRIPT_HTTP_PLUGIN, TSX_HTTP_PLUGIN } from './node.js';
|
|
7
|
+
/**
|
|
8
|
+
* File-extension → HTTP language plugin registry. The top-level
|
|
9
|
+
* orchestrator (`http-route-extractor.ts`) looks up the plugin for each
|
|
10
|
+
* file it visits and delegates the tree-sitter scanning to the plugin.
|
|
11
|
+
*
|
|
12
|
+
* Keys are lowercase extensions including the leading dot. To add a
|
|
13
|
+
* new language, drop a `http-patterns/<lang>.ts` that exports a
|
|
14
|
+
* `HttpLanguagePlugin`, import it here and register the extension(s).
|
|
15
|
+
* No edits to `http-route-extractor.ts` are required.
|
|
16
|
+
*/
|
|
17
|
+
const REGISTRY = {
|
|
18
|
+
'.java': JAVA_HTTP_PLUGIN,
|
|
19
|
+
'.go': GO_HTTP_PLUGIN,
|
|
20
|
+
'.py': PYTHON_HTTP_PLUGIN,
|
|
21
|
+
'.php': PHP_HTTP_PLUGIN,
|
|
22
|
+
'.js': JAVASCRIPT_HTTP_PLUGIN,
|
|
23
|
+
'.jsx': JAVASCRIPT_HTTP_PLUGIN,
|
|
24
|
+
'.ts': TYPESCRIPT_HTTP_PLUGIN,
|
|
25
|
+
'.tsx': TSX_HTTP_PLUGIN,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Glob for files worth scanning for HTTP routes. Kept alongside the
|
|
29
|
+
* registry so adding a new language widens the glob in one edit.
|
|
30
|
+
*
|
|
31
|
+
* `.vue` / `.svelte` files are intentionally omitted for the source-scan
|
|
32
|
+
* path — they need their own grammar-aware extraction and the existing
|
|
33
|
+
* regex fallback for them was never very accurate. The graph-assisted
|
|
34
|
+
* Strategy A still handles them via the ingestion pipeline.
|
|
35
|
+
*/
|
|
36
|
+
export const HTTP_SCAN_GLOB = '**/*.{ts,tsx,js,jsx,java,go,py,php}';
|
|
37
|
+
/**
|
|
38
|
+
* Return the HTTP plugin registered for the given file's extension,
|
|
39
|
+
* or `undefined` if the extension is not registered.
|
|
40
|
+
*/
|
|
41
|
+
export function getPluginForFile(rel) {
|
|
42
|
+
const ext = path.extname(rel).toLowerCase();
|
|
43
|
+
return REGISTRY[ext];
|
|
44
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import Java from 'tree-sitter-java';
|
|
2
|
+
import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
|
|
3
|
+
/**
|
|
4
|
+
* Java HTTP plugin. Handles:
|
|
5
|
+
* - Spring `@RequestMapping` class prefixes + `@(Get|Post|...)Mapping` method annotations
|
|
6
|
+
* - Spring `RestTemplate.getForObject/...`, `WebClient.method(HttpMethod.X, ...)`
|
|
7
|
+
* - OkHttp `new Request.Builder().url("...")`
|
|
8
|
+
*
|
|
9
|
+
* The plugin runs two pattern bundles: one to collect class-level
|
|
10
|
+
* `@RequestMapping` prefixes keyed by the enclosing class node, and a
|
|
11
|
+
* second to match method-level annotations. The `scan` function walks
|
|
12
|
+
* up from each matched annotation to find its enclosing class and
|
|
13
|
+
* combines the prefix with the method path.
|
|
14
|
+
*/
|
|
15
|
+
const METHOD_ANNOTATION_TO_HTTP = {
|
|
16
|
+
GetMapping: 'GET',
|
|
17
|
+
PostMapping: 'POST',
|
|
18
|
+
PutMapping: 'PUT',
|
|
19
|
+
DeleteMapping: 'DELETE',
|
|
20
|
+
PatchMapping: 'PATCH',
|
|
21
|
+
};
|
|
22
|
+
// ─── Provider: Spring class-level @RequestMapping prefix ──────────────
|
|
23
|
+
const SPRING_CLASS_PREFIX_PATTERNS = compilePatterns({
|
|
24
|
+
name: 'java-spring-class-prefix',
|
|
25
|
+
language: Java,
|
|
26
|
+
patterns: [
|
|
27
|
+
{
|
|
28
|
+
meta: {},
|
|
29
|
+
query: `
|
|
30
|
+
(class_declaration
|
|
31
|
+
(modifiers
|
|
32
|
+
(annotation
|
|
33
|
+
name: (identifier) @ann (#eq? @ann "RequestMapping")
|
|
34
|
+
arguments: (annotation_argument_list (string_literal) @prefix)))) @class
|
|
35
|
+
`,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
// ─── Provider: Spring @(Get|Post|...)Mapping method annotations ───────
|
|
40
|
+
const SPRING_METHOD_ROUTE_PATTERNS = compilePatterns({
|
|
41
|
+
name: 'java-spring-method-route',
|
|
42
|
+
language: Java,
|
|
43
|
+
patterns: [
|
|
44
|
+
{
|
|
45
|
+
meta: {},
|
|
46
|
+
query: `
|
|
47
|
+
(method_declaration
|
|
48
|
+
(modifiers
|
|
49
|
+
(annotation
|
|
50
|
+
name: (identifier) @ann (#match? @ann "^(Get|Post|Put|Delete|Patch)Mapping$")
|
|
51
|
+
arguments: (annotation_argument_list (string_literal) @path)))
|
|
52
|
+
name: (identifier) @method_name) @method
|
|
53
|
+
`,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
// ─── Consumer: Spring RestTemplate (object-named + method-named) ──────
|
|
58
|
+
// RestTemplate.getForObject / getForEntity → GET
|
|
59
|
+
// RestTemplate.postForObject / postForEntity → POST
|
|
60
|
+
// RestTemplate.put → PUT
|
|
61
|
+
// RestTemplate.delete → DELETE
|
|
62
|
+
// RestTemplate.patchForObject → PATCH
|
|
63
|
+
const REST_TEMPLATE_TO_HTTP = {
|
|
64
|
+
getForObject: 'GET',
|
|
65
|
+
getForEntity: 'GET',
|
|
66
|
+
postForObject: 'POST',
|
|
67
|
+
postForEntity: 'POST',
|
|
68
|
+
put: 'PUT',
|
|
69
|
+
delete: 'DELETE',
|
|
70
|
+
patchForObject: 'PATCH',
|
|
71
|
+
};
|
|
72
|
+
const REST_TEMPLATE_PATTERNS = compilePatterns({
|
|
73
|
+
name: 'java-rest-template',
|
|
74
|
+
language: Java,
|
|
75
|
+
patterns: [
|
|
76
|
+
{
|
|
77
|
+
meta: { framework: 'spring-rest-template' },
|
|
78
|
+
query: `
|
|
79
|
+
(method_invocation
|
|
80
|
+
object: (identifier) @obj (#eq? @obj "restTemplate")
|
|
81
|
+
name: (identifier) @method
|
|
82
|
+
arguments: (argument_list . (string_literal) @path))
|
|
83
|
+
`,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
// ─── Consumer: Spring WebClient — webClient.method(HttpMethod.X, "path") ─
|
|
88
|
+
const WEB_CLIENT_PATTERNS = compilePatterns({
|
|
89
|
+
name: 'java-web-client',
|
|
90
|
+
language: Java,
|
|
91
|
+
patterns: [
|
|
92
|
+
{
|
|
93
|
+
meta: {},
|
|
94
|
+
query: `
|
|
95
|
+
(method_invocation
|
|
96
|
+
object: (identifier) @obj (#eq? @obj "webClient")
|
|
97
|
+
name: (identifier) @method (#eq? @method "method")
|
|
98
|
+
arguments: (argument_list
|
|
99
|
+
(field_access
|
|
100
|
+
object: (identifier) @httpMethodCls (#eq? @httpMethodCls "HttpMethod")
|
|
101
|
+
field: (identifier) @http_method)
|
|
102
|
+
(string_literal) @path))
|
|
103
|
+
`,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
// ─── Consumer: OkHttp `new Request.Builder().url("path")` ─────────────
|
|
108
|
+
// Note: `Request.Builder` is a `scoped_type_identifier` whose text includes
|
|
109
|
+
// the dot, so `#eq?` against the literal string matches cleanly (no need
|
|
110
|
+
// to escape a regex dot).
|
|
111
|
+
const OK_HTTP_PATTERNS = compilePatterns({
|
|
112
|
+
name: 'java-okhttp',
|
|
113
|
+
language: Java,
|
|
114
|
+
patterns: [
|
|
115
|
+
{
|
|
116
|
+
meta: {},
|
|
117
|
+
query: `
|
|
118
|
+
(method_invocation
|
|
119
|
+
object: (object_creation_expression
|
|
120
|
+
type: (scoped_type_identifier) @type (#eq? @type "Request.Builder"))
|
|
121
|
+
name: (identifier) @method (#eq? @method "url")
|
|
122
|
+
arguments: (argument_list . (string_literal) @path))
|
|
123
|
+
`,
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
/**
|
|
128
|
+
* Find the nearest enclosing class_declaration ancestor for a node, or
|
|
129
|
+
* null if the node is top-level. Tree-sitter's SyntaxNode.parent walks
|
|
130
|
+
* one level at a time.
|
|
131
|
+
*/
|
|
132
|
+
function findEnclosingClass(node) {
|
|
133
|
+
let cur = node.parent;
|
|
134
|
+
while (cur) {
|
|
135
|
+
if (cur.type === 'class_declaration')
|
|
136
|
+
return cur;
|
|
137
|
+
cur = cur.parent;
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Join a class-level prefix and a method-level path into a single URL
|
|
143
|
+
* path. Mirrors the semantics of the original regex implementation:
|
|
144
|
+
* strip trailing slashes on the prefix, then ensure a single slash
|
|
145
|
+
* between prefix and method path.
|
|
146
|
+
*/
|
|
147
|
+
function joinPath(prefix, methodPath) {
|
|
148
|
+
const cleanPrefix = prefix.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
149
|
+
const cleanSub = methodPath.replace(/^\/+/, '');
|
|
150
|
+
if (!cleanPrefix)
|
|
151
|
+
return `/${cleanSub}`;
|
|
152
|
+
return `/${cleanPrefix}/${cleanSub}`;
|
|
153
|
+
}
|
|
154
|
+
export const JAVA_HTTP_PLUGIN = {
|
|
155
|
+
name: 'java-http',
|
|
156
|
+
language: Java,
|
|
157
|
+
scan(tree) {
|
|
158
|
+
const out = [];
|
|
159
|
+
// ─── Providers: Spring class prefix + method annotations ────────
|
|
160
|
+
const prefixByClassId = new Map();
|
|
161
|
+
for (const match of runCompiledPatterns(SPRING_CLASS_PREFIX_PATTERNS, tree)) {
|
|
162
|
+
const prefixNode = match.captures.prefix;
|
|
163
|
+
const classNode = match.captures.class;
|
|
164
|
+
if (!prefixNode || !classNode)
|
|
165
|
+
continue;
|
|
166
|
+
const prefix = unquoteLiteral(prefixNode.text);
|
|
167
|
+
if (prefix !== null)
|
|
168
|
+
prefixByClassId.set(classNode.id, prefix);
|
|
169
|
+
}
|
|
170
|
+
for (const match of runCompiledPatterns(SPRING_METHOD_ROUTE_PATTERNS, tree)) {
|
|
171
|
+
const annNode = match.captures.ann;
|
|
172
|
+
const pathNode = match.captures.path;
|
|
173
|
+
const nameNode = match.captures.method_name;
|
|
174
|
+
const methodNode = match.captures.method;
|
|
175
|
+
if (!annNode || !pathNode || !methodNode)
|
|
176
|
+
continue;
|
|
177
|
+
const httpMethod = METHOD_ANNOTATION_TO_HTTP[annNode.text];
|
|
178
|
+
if (!httpMethod)
|
|
179
|
+
continue;
|
|
180
|
+
const rawPath = unquoteLiteral(pathNode.text);
|
|
181
|
+
if (rawPath === null)
|
|
182
|
+
continue;
|
|
183
|
+
const enclosingClass = findEnclosingClass(methodNode);
|
|
184
|
+
const prefix = enclosingClass ? (prefixByClassId.get(enclosingClass.id) ?? '') : '';
|
|
185
|
+
const fullPath = joinPath(prefix, rawPath);
|
|
186
|
+
out.push({
|
|
187
|
+
role: 'provider',
|
|
188
|
+
framework: 'spring',
|
|
189
|
+
method: httpMethod,
|
|
190
|
+
path: fullPath,
|
|
191
|
+
name: nameNode?.text ?? null,
|
|
192
|
+
confidence: 0.8,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
// ─── Consumers: RestTemplate ────────────────────────────────────
|
|
196
|
+
for (const match of runCompiledPatterns(REST_TEMPLATE_PATTERNS, tree)) {
|
|
197
|
+
const methodNode = match.captures.method;
|
|
198
|
+
const pathNode = match.captures.path;
|
|
199
|
+
if (!methodNode || !pathNode)
|
|
200
|
+
continue;
|
|
201
|
+
const httpMethod = REST_TEMPLATE_TO_HTTP[methodNode.text];
|
|
202
|
+
if (!httpMethod)
|
|
203
|
+
continue;
|
|
204
|
+
const path = unquoteLiteral(pathNode.text);
|
|
205
|
+
if (path === null)
|
|
206
|
+
continue;
|
|
207
|
+
out.push({
|
|
208
|
+
role: 'consumer',
|
|
209
|
+
framework: 'spring-rest-template',
|
|
210
|
+
method: httpMethod,
|
|
211
|
+
path,
|
|
212
|
+
name: null,
|
|
213
|
+
confidence: 0.7,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
// ─── Consumers: WebClient.method(HttpMethod.X, "path") ──────────
|
|
217
|
+
for (const match of runCompiledPatterns(WEB_CLIENT_PATTERNS, tree)) {
|
|
218
|
+
const httpMethodNode = match.captures.http_method;
|
|
219
|
+
const pathNode = match.captures.path;
|
|
220
|
+
if (!httpMethodNode || !pathNode)
|
|
221
|
+
continue;
|
|
222
|
+
const path = unquoteLiteral(pathNode.text);
|
|
223
|
+
if (path === null)
|
|
224
|
+
continue;
|
|
225
|
+
out.push({
|
|
226
|
+
role: 'consumer',
|
|
227
|
+
framework: 'spring-web-client',
|
|
228
|
+
method: httpMethodNode.text.toUpperCase(),
|
|
229
|
+
path,
|
|
230
|
+
name: null,
|
|
231
|
+
confidence: 0.7,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
// ─── Consumers: OkHttp Request.Builder().url("path") ────────────
|
|
235
|
+
for (const match of runCompiledPatterns(OK_HTTP_PATTERNS, tree)) {
|
|
236
|
+
const pathNode = match.captures.path;
|
|
237
|
+
if (!pathNode)
|
|
238
|
+
continue;
|
|
239
|
+
const path = unquoteLiteral(pathNode.text);
|
|
240
|
+
if (path === null)
|
|
241
|
+
continue;
|
|
242
|
+
out.push({
|
|
243
|
+
role: 'consumer',
|
|
244
|
+
framework: 'okhttp',
|
|
245
|
+
method: 'GET',
|
|
246
|
+
path,
|
|
247
|
+
name: null,
|
|
248
|
+
confidence: 0.7,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return out;
|
|
252
|
+
},
|
|
253
|
+
};
|