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,354 @@
|
|
|
1
|
+
import JavaScript from 'tree-sitter-javascript';
|
|
2
|
+
import TypeScript from 'tree-sitter-typescript';
|
|
3
|
+
import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
|
|
4
|
+
/**
|
|
5
|
+
* Node.js / TypeScript HTTP plugin family. Handles:
|
|
6
|
+
* - NestJS `@Controller('prefix')` classes with `@Get(':id')` methods
|
|
7
|
+
* - Express `router.get(...)` / `app.post(...)` providers
|
|
8
|
+
* - `fetch(url)` / `fetch(url, { method: 'POST' })` consumers
|
|
9
|
+
* - `axios.get(url)` / `axios.delete(url)` consumers
|
|
10
|
+
*
|
|
11
|
+
* Because the JavaScript and TypeScript tree-sitter grammars share
|
|
12
|
+
* node type names for every construct we query, pattern sources are
|
|
13
|
+
* defined once and compiled against each grammar variant. The plugin
|
|
14
|
+
* exports three `HttpLanguagePlugin`s (JS, TS, TSX) that share the
|
|
15
|
+
* same `scan` function but bind to different grammars.
|
|
16
|
+
*/
|
|
17
|
+
// ─── Provider: NestJS — class-level @Controller('prefix') ────────────
|
|
18
|
+
// In tree-sitter-typescript decorators are NOT children of
|
|
19
|
+
// class_declaration / method_definition — they're siblings in the
|
|
20
|
+
// surrounding class_body / program node. We therefore match the
|
|
21
|
+
// decorator standalone and walk to its related class/method in JS.
|
|
22
|
+
const NEST_CONTROLLER_SPEC = {
|
|
23
|
+
meta: {},
|
|
24
|
+
query: `
|
|
25
|
+
(decorator
|
|
26
|
+
(call_expression
|
|
27
|
+
function: (identifier) @dec (#eq? @dec "Controller")
|
|
28
|
+
arguments: (arguments . [(string) (template_string)] @prefix))) @ctrl_decorator
|
|
29
|
+
`,
|
|
30
|
+
};
|
|
31
|
+
// ─── Provider: NestJS — method-level @Get/@Post/... decorators ───────
|
|
32
|
+
// Matches either `@Get('path')` or `@Get()`. The `@path` capture is
|
|
33
|
+
// optional — when the first argument isn't a string, the plugin falls
|
|
34
|
+
// back to '/' for the method-level path.
|
|
35
|
+
const NEST_METHOD_SPEC = {
|
|
36
|
+
meta: {},
|
|
37
|
+
query: `
|
|
38
|
+
(decorator
|
|
39
|
+
(call_expression
|
|
40
|
+
function: (identifier) @dec (#match? @dec "^(Get|Post|Put|Delete|Patch)$")
|
|
41
|
+
arguments: (arguments) @args)) @method_decorator
|
|
42
|
+
`,
|
|
43
|
+
};
|
|
44
|
+
// ─── Provider: Express — router.get/app.post/... ─────────────────────
|
|
45
|
+
const EXPRESS_SPEC = {
|
|
46
|
+
meta: {},
|
|
47
|
+
query: `
|
|
48
|
+
(call_expression
|
|
49
|
+
function: (member_expression
|
|
50
|
+
object: (identifier) @obj (#match? @obj "^(router|app)$")
|
|
51
|
+
property: (property_identifier) @http_method (#match? @http_method "^(get|post|put|delete|patch)$"))
|
|
52
|
+
arguments: (arguments . [(string) (template_string)] @path))
|
|
53
|
+
`,
|
|
54
|
+
};
|
|
55
|
+
// ─── Consumer: fetch(url) with NO options ─────────────────────────────
|
|
56
|
+
const FETCH_NO_OPTIONS_SPEC = {
|
|
57
|
+
meta: {},
|
|
58
|
+
query: `
|
|
59
|
+
(call_expression
|
|
60
|
+
function: (identifier) @fn (#eq? @fn "fetch")
|
|
61
|
+
arguments: (arguments . [(string) (template_string)] @path .))
|
|
62
|
+
`,
|
|
63
|
+
};
|
|
64
|
+
// ─── Consumer: fetch(url, { method: 'X', ... }) ──────────────────────
|
|
65
|
+
const FETCH_WITH_OPTIONS_SPEC = {
|
|
66
|
+
meta: {},
|
|
67
|
+
query: `
|
|
68
|
+
(call_expression
|
|
69
|
+
function: (identifier) @fn (#eq? @fn "fetch")
|
|
70
|
+
arguments: (arguments
|
|
71
|
+
. [(string) (template_string)] @path
|
|
72
|
+
(object
|
|
73
|
+
(pair
|
|
74
|
+
key: (property_identifier) @key (#eq? @key "method")
|
|
75
|
+
value: (string) @http_method))))
|
|
76
|
+
`,
|
|
77
|
+
};
|
|
78
|
+
// ─── Consumer: axios.get/post/... ────────────────────────────────────
|
|
79
|
+
const AXIOS_SPEC = {
|
|
80
|
+
meta: {},
|
|
81
|
+
query: `
|
|
82
|
+
(call_expression
|
|
83
|
+
function: (member_expression
|
|
84
|
+
object: (identifier) @obj (#eq? @obj "axios")
|
|
85
|
+
property: (property_identifier) @http_method (#match? @http_method "^(get|post|put|delete|patch)$"))
|
|
86
|
+
arguments: (arguments . [(string) (template_string)] @path))
|
|
87
|
+
`,
|
|
88
|
+
};
|
|
89
|
+
function compileBundle(language, name) {
|
|
90
|
+
const mk = (spec, suffix) => compilePatterns({
|
|
91
|
+
name: `${name}-${suffix}`,
|
|
92
|
+
language,
|
|
93
|
+
patterns: [spec],
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
controller: mk(NEST_CONTROLLER_SPEC, 'nest-controller'),
|
|
97
|
+
methodDecorator: mk(NEST_METHOD_SPEC, 'nest-method-decorator'),
|
|
98
|
+
express: mk(EXPRESS_SPEC, 'express'),
|
|
99
|
+
fetchNoOptions: mk(FETCH_NO_OPTIONS_SPEC, 'fetch-no-options'),
|
|
100
|
+
fetchWithOptions: mk(FETCH_WITH_OPTIONS_SPEC, 'fetch-with-options'),
|
|
101
|
+
axios: mk(AXIOS_SPEC, 'axios'),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const JAVASCRIPT_BUNDLE = compileBundle(JavaScript, 'javascript-http');
|
|
105
|
+
const TYPESCRIPT_BUNDLE = compileBundle(TypeScript.typescript, 'typescript-http');
|
|
106
|
+
const TSX_BUNDLE = compileBundle(TypeScript.tsx, 'tsx-http');
|
|
107
|
+
const NEST_DECORATOR_TO_HTTP = {
|
|
108
|
+
Get: 'GET',
|
|
109
|
+
Post: 'POST',
|
|
110
|
+
Put: 'PUT',
|
|
111
|
+
Delete: 'DELETE',
|
|
112
|
+
Patch: 'PATCH',
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Find the nearest enclosing class_declaration for a node, or null.
|
|
116
|
+
*/
|
|
117
|
+
function findEnclosingClass(node) {
|
|
118
|
+
let cur = node.parent;
|
|
119
|
+
while (cur) {
|
|
120
|
+
if (cur.type === 'class_declaration')
|
|
121
|
+
return cur;
|
|
122
|
+
cur = cur.parent;
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
function joinPath(prefix, sub) {
|
|
127
|
+
const cleanPrefix = prefix.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
128
|
+
const cleanSub = sub.replace(/^\/+/, '');
|
|
129
|
+
if (!cleanPrefix)
|
|
130
|
+
return `/${cleanSub}`;
|
|
131
|
+
return `/${cleanPrefix}/${cleanSub}`;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* For a standalone `decorator` node (child of class_body / program),
|
|
135
|
+
* find the related `class_declaration` node that it decorates. In
|
|
136
|
+
* tree-sitter-typescript the decorator is placed before the class
|
|
137
|
+
* declaration as a sibling (when decorating a class) or inside the
|
|
138
|
+
* class_body before a method_definition (when decorating a method);
|
|
139
|
+
* we walk the parent chain until we find the enclosing class.
|
|
140
|
+
*/
|
|
141
|
+
function findDecoratedClass(decoratorNode) {
|
|
142
|
+
const parent = decoratorNode.parent;
|
|
143
|
+
if (!parent)
|
|
144
|
+
return null;
|
|
145
|
+
// Case 1: decorator is a sibling of the class_declaration at program /
|
|
146
|
+
// export_statement level. Walk forward through siblings until we find
|
|
147
|
+
// the class_declaration this decorator belongs to.
|
|
148
|
+
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
149
|
+
const child = parent.namedChild(i);
|
|
150
|
+
if (child && child.id === decoratorNode.id) {
|
|
151
|
+
for (let j = i + 1; j < parent.namedChildCount; j++) {
|
|
152
|
+
const next = parent.namedChild(j);
|
|
153
|
+
if (!next)
|
|
154
|
+
continue;
|
|
155
|
+
if (next.type === 'decorator')
|
|
156
|
+
continue; // adjacent decorators stack
|
|
157
|
+
if (next.type === 'class_declaration')
|
|
158
|
+
return next;
|
|
159
|
+
if (next.type === 'export_statement') {
|
|
160
|
+
// `export class Foo { ... }` wraps the declaration.
|
|
161
|
+
for (let k = 0; k < next.namedChildCount; k++) {
|
|
162
|
+
const inner = next.namedChild(k);
|
|
163
|
+
if (inner?.type === 'class_declaration')
|
|
164
|
+
return inner;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Case 2: decorator is inside a class_body (decorating a method) —
|
|
173
|
+
// walk up to the enclosing class_declaration.
|
|
174
|
+
return findEnclosingClass(decoratorNode);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* For a method-level decorator node (child of class_body before a
|
|
178
|
+
* method_definition), find the method_definition it decorates.
|
|
179
|
+
*/
|
|
180
|
+
function findDecoratedMethod(decoratorNode) {
|
|
181
|
+
const parent = decoratorNode.parent;
|
|
182
|
+
if (!parent || parent.type !== 'class_body')
|
|
183
|
+
return null;
|
|
184
|
+
for (let i = 0; i < parent.namedChildCount; i++) {
|
|
185
|
+
const child = parent.namedChild(i);
|
|
186
|
+
if (child && child.id === decoratorNode.id) {
|
|
187
|
+
for (let j = i + 1; j < parent.namedChildCount; j++) {
|
|
188
|
+
const next = parent.namedChild(j);
|
|
189
|
+
if (!next)
|
|
190
|
+
continue;
|
|
191
|
+
if (next.type === 'decorator')
|
|
192
|
+
continue;
|
|
193
|
+
if (next.type === 'method_definition')
|
|
194
|
+
return next;
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
function scanBundle(bundle, tree) {
|
|
203
|
+
const out = [];
|
|
204
|
+
// NestJS: collect `@Controller('prefix')` class decorators, keyed by
|
|
205
|
+
// the `class_declaration` they decorate.
|
|
206
|
+
const prefixByClassId = new Map();
|
|
207
|
+
for (const match of runCompiledPatterns(bundle.controller, tree)) {
|
|
208
|
+
const prefixNode = match.captures.prefix;
|
|
209
|
+
const decoratorNode = match.captures.ctrl_decorator;
|
|
210
|
+
if (!prefixNode || !decoratorNode)
|
|
211
|
+
continue;
|
|
212
|
+
const prefix = unquoteLiteral(prefixNode.text);
|
|
213
|
+
if (prefix === null)
|
|
214
|
+
continue;
|
|
215
|
+
const classNode = findDecoratedClass(decoratorNode);
|
|
216
|
+
if (!classNode)
|
|
217
|
+
continue;
|
|
218
|
+
prefixByClassId.set(classNode.id, prefix);
|
|
219
|
+
}
|
|
220
|
+
// NestJS: method-level @Get/@Post/... decorators. The decorator's
|
|
221
|
+
// arguments list may be empty (`@Get()`), a string (`@Get('path')`),
|
|
222
|
+
// or something else (which we skip).
|
|
223
|
+
for (const match of runCompiledPatterns(bundle.methodDecorator, tree)) {
|
|
224
|
+
const decNode = match.captures.dec;
|
|
225
|
+
const argsNode = match.captures.args;
|
|
226
|
+
const decoratorNode = match.captures.method_decorator;
|
|
227
|
+
if (!decNode || !argsNode || !decoratorNode)
|
|
228
|
+
continue;
|
|
229
|
+
const httpMethod = NEST_DECORATOR_TO_HTTP[decNode.text];
|
|
230
|
+
if (!httpMethod)
|
|
231
|
+
continue;
|
|
232
|
+
const methodNode = findDecoratedMethod(decoratorNode);
|
|
233
|
+
if (!methodNode)
|
|
234
|
+
continue;
|
|
235
|
+
const enclosingClass = findEnclosingClass(methodNode);
|
|
236
|
+
// Only emit NestJS detections when the class actually has a
|
|
237
|
+
// @Controller decorator — without it, the match is almost certainly
|
|
238
|
+
// something else (e.g. an unrelated library using similar names).
|
|
239
|
+
if (!enclosingClass || !prefixByClassId.has(enclosingClass.id))
|
|
240
|
+
continue;
|
|
241
|
+
const prefix = prefixByClassId.get(enclosingClass.id) ?? '';
|
|
242
|
+
let rawPath = '/';
|
|
243
|
+
const firstArg = argsNode.namedChild(0);
|
|
244
|
+
if (firstArg && (firstArg.type === 'string' || firstArg.type === 'template_string')) {
|
|
245
|
+
const unquoted = unquoteLiteral(firstArg.text);
|
|
246
|
+
if (unquoted !== null)
|
|
247
|
+
rawPath = unquoted;
|
|
248
|
+
}
|
|
249
|
+
// Get the method name from the decorated method_definition.
|
|
250
|
+
const methodNameNode = methodNode.childForFieldName('name');
|
|
251
|
+
const name = methodNameNode?.text ?? null;
|
|
252
|
+
out.push({
|
|
253
|
+
role: 'provider',
|
|
254
|
+
framework: 'nest',
|
|
255
|
+
method: httpMethod,
|
|
256
|
+
path: joinPath(prefix, rawPath),
|
|
257
|
+
name,
|
|
258
|
+
confidence: 0.8,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
// Express: router/app.<verb>(...)
|
|
262
|
+
for (const match of runCompiledPatterns(bundle.express, tree)) {
|
|
263
|
+
const methodNode = match.captures.http_method;
|
|
264
|
+
const pathNode = match.captures.path;
|
|
265
|
+
if (!methodNode || !pathNode)
|
|
266
|
+
continue;
|
|
267
|
+
const path = unquoteLiteral(pathNode.text);
|
|
268
|
+
if (path === null)
|
|
269
|
+
continue;
|
|
270
|
+
out.push({
|
|
271
|
+
role: 'provider',
|
|
272
|
+
framework: 'express',
|
|
273
|
+
method: methodNode.text.toUpperCase(),
|
|
274
|
+
path,
|
|
275
|
+
name: 'handler',
|
|
276
|
+
confidence: 0.8,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
// Consumer: fetch with options { method: 'X' }
|
|
280
|
+
const fetchSeen = new Set();
|
|
281
|
+
for (const match of runCompiledPatterns(bundle.fetchWithOptions, tree)) {
|
|
282
|
+
const pathNode = match.captures.path;
|
|
283
|
+
const methodNode = match.captures.http_method;
|
|
284
|
+
if (!pathNode || !methodNode)
|
|
285
|
+
continue;
|
|
286
|
+
const path = unquoteLiteral(pathNode.text);
|
|
287
|
+
const method = unquoteLiteral(methodNode.text);
|
|
288
|
+
if (path === null || method === null)
|
|
289
|
+
continue;
|
|
290
|
+
fetchSeen.add(pathNode.id);
|
|
291
|
+
out.push({
|
|
292
|
+
role: 'consumer',
|
|
293
|
+
framework: 'fetch',
|
|
294
|
+
method: method.toUpperCase(),
|
|
295
|
+
path,
|
|
296
|
+
name: null,
|
|
297
|
+
confidence: 0.7,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
// Consumer: plain fetch(path) — default GET. Skip path nodes we already
|
|
301
|
+
// matched with the options variant so we don't double-emit.
|
|
302
|
+
for (const match of runCompiledPatterns(bundle.fetchNoOptions, tree)) {
|
|
303
|
+
const pathNode = match.captures.path;
|
|
304
|
+
if (!pathNode)
|
|
305
|
+
continue;
|
|
306
|
+
if (fetchSeen.has(pathNode.id))
|
|
307
|
+
continue;
|
|
308
|
+
const path = unquoteLiteral(pathNode.text);
|
|
309
|
+
if (path === null)
|
|
310
|
+
continue;
|
|
311
|
+
out.push({
|
|
312
|
+
role: 'consumer',
|
|
313
|
+
framework: 'fetch',
|
|
314
|
+
method: 'GET',
|
|
315
|
+
path,
|
|
316
|
+
name: null,
|
|
317
|
+
confidence: 0.7,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
// Consumer: axios.<verb>(url)
|
|
321
|
+
for (const match of runCompiledPatterns(bundle.axios, tree)) {
|
|
322
|
+
const methodNode = match.captures.http_method;
|
|
323
|
+
const pathNode = match.captures.path;
|
|
324
|
+
if (!methodNode || !pathNode)
|
|
325
|
+
continue;
|
|
326
|
+
const path = unquoteLiteral(pathNode.text);
|
|
327
|
+
if (path === null)
|
|
328
|
+
continue;
|
|
329
|
+
out.push({
|
|
330
|
+
role: 'consumer',
|
|
331
|
+
framework: 'axios',
|
|
332
|
+
method: methodNode.text.toUpperCase(),
|
|
333
|
+
path,
|
|
334
|
+
name: null,
|
|
335
|
+
confidence: 0.7,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
export const JAVASCRIPT_HTTP_PLUGIN = {
|
|
341
|
+
name: 'javascript-http',
|
|
342
|
+
language: JavaScript,
|
|
343
|
+
scan: (tree) => scanBundle(JAVASCRIPT_BUNDLE, tree),
|
|
344
|
+
};
|
|
345
|
+
export const TYPESCRIPT_HTTP_PLUGIN = {
|
|
346
|
+
name: 'typescript-http',
|
|
347
|
+
language: TypeScript.typescript,
|
|
348
|
+
scan: (tree) => scanBundle(TYPESCRIPT_BUNDLE, tree),
|
|
349
|
+
};
|
|
350
|
+
export const TSX_HTTP_PLUGIN = {
|
|
351
|
+
name: 'tsx-http',
|
|
352
|
+
language: TypeScript.tsx,
|
|
353
|
+
scan: (tree) => scanBundle(TSX_BUNDLE, tree),
|
|
354
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import PHP from 'tree-sitter-php';
|
|
2
|
+
import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
|
|
3
|
+
/**
|
|
4
|
+
* PHP HTTP plugin — Laravel `Route::get/post/...` declarations.
|
|
5
|
+
*
|
|
6
|
+
* The pipeline already uses `PHP.php_only` for ingesting plain `.php`
|
|
7
|
+
* files (see `core/tree-sitter/parser-loader.ts`), and we do the same
|
|
8
|
+
* here so Laravel route files are parsed with the right grammar dialect.
|
|
9
|
+
*/
|
|
10
|
+
const LARAVEL_PATTERNS = compilePatterns({
|
|
11
|
+
name: 'php-laravel',
|
|
12
|
+
language: PHP.php_only,
|
|
13
|
+
patterns: [
|
|
14
|
+
{
|
|
15
|
+
meta: {},
|
|
16
|
+
query: `
|
|
17
|
+
(scoped_call_expression
|
|
18
|
+
scope: (name) @scope (#eq? @scope "Route")
|
|
19
|
+
name: (name) @method (#match? @method "^(get|post|put|delete|patch)$")
|
|
20
|
+
arguments: (arguments . (argument (string) @path)))
|
|
21
|
+
`,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* Extract the inner text of a PHP `string` node. The tree-sitter-php
|
|
27
|
+
* grammar wraps single / double-quoted literals differently depending
|
|
28
|
+
* on content; we try both the raw `text` (with quotes) through
|
|
29
|
+
* `unquoteLiteral`, and a fallback via the `string_value` / `string_content`
|
|
30
|
+
* child nodes.
|
|
31
|
+
*/
|
|
32
|
+
function phpStringText(node) {
|
|
33
|
+
// Most single-quoted strings expose their inner content through the
|
|
34
|
+
// full node text (including quotes), which unquoteLiteral strips.
|
|
35
|
+
const direct = unquoteLiteral(node.text);
|
|
36
|
+
if (direct !== null && direct !== node.text)
|
|
37
|
+
return direct;
|
|
38
|
+
// Fall back to child string_content / string_value node if present.
|
|
39
|
+
for (const child of node.children) {
|
|
40
|
+
if (child.type === 'string_content' || child.type === 'string_value') {
|
|
41
|
+
return child.text;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return direct;
|
|
45
|
+
}
|
|
46
|
+
export const PHP_HTTP_PLUGIN = {
|
|
47
|
+
name: 'php-http',
|
|
48
|
+
language: PHP.php_only,
|
|
49
|
+
scan(tree) {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const match of runCompiledPatterns(LARAVEL_PATTERNS, tree)) {
|
|
52
|
+
const methodNode = match.captures.method;
|
|
53
|
+
const pathNode = match.captures.path;
|
|
54
|
+
if (!methodNode || !pathNode)
|
|
55
|
+
continue;
|
|
56
|
+
const path = phpStringText(pathNode);
|
|
57
|
+
if (path === null)
|
|
58
|
+
continue;
|
|
59
|
+
out.push({
|
|
60
|
+
role: 'provider',
|
|
61
|
+
framework: 'laravel',
|
|
62
|
+
method: methodNode.text.toUpperCase(),
|
|
63
|
+
path,
|
|
64
|
+
name: 'route',
|
|
65
|
+
confidence: 0.8,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import Python from 'tree-sitter-python';
|
|
2
|
+
import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
|
|
3
|
+
/**
|
|
4
|
+
* Python HTTP plugin. Handles:
|
|
5
|
+
* - FastAPI `@app.get("/path")` provider decorators
|
|
6
|
+
* - `requests.get/post/...("url")` consumer calls
|
|
7
|
+
* - Generic `requests.request("METHOD", "url")` consumer calls
|
|
8
|
+
*/
|
|
9
|
+
const FASTAPI_VERBS = {
|
|
10
|
+
get: 'GET',
|
|
11
|
+
post: 'POST',
|
|
12
|
+
put: 'PUT',
|
|
13
|
+
delete: 'DELETE',
|
|
14
|
+
patch: 'PATCH',
|
|
15
|
+
};
|
|
16
|
+
// ─── Provider: FastAPI @app.get/... ──────────────────────────────────
|
|
17
|
+
const FASTAPI_PATTERNS = compilePatterns({
|
|
18
|
+
name: 'python-fastapi',
|
|
19
|
+
language: Python,
|
|
20
|
+
patterns: [
|
|
21
|
+
{
|
|
22
|
+
meta: {},
|
|
23
|
+
query: `
|
|
24
|
+
(decorator
|
|
25
|
+
(call
|
|
26
|
+
function: (attribute
|
|
27
|
+
object: (identifier) @obj (#eq? @obj "app")
|
|
28
|
+
attribute: (identifier) @method (#match? @method "^(get|post|put|delete|patch)$"))
|
|
29
|
+
arguments: (argument_list . (string) @path)))
|
|
30
|
+
`,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
// ─── Consumer: requests.get/post/... ──────────────────────────────────
|
|
35
|
+
const REQUESTS_VERB_PATTERNS = compilePatterns({
|
|
36
|
+
name: 'python-requests-verb',
|
|
37
|
+
language: Python,
|
|
38
|
+
patterns: [
|
|
39
|
+
{
|
|
40
|
+
meta: {},
|
|
41
|
+
query: `
|
|
42
|
+
(call
|
|
43
|
+
function: (attribute
|
|
44
|
+
object: (identifier) @obj (#eq? @obj "requests")
|
|
45
|
+
attribute: (identifier) @method (#match? @method "^(get|post|put|delete|patch)$"))
|
|
46
|
+
arguments: (argument_list . (string) @path))
|
|
47
|
+
`,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
// ─── Consumer: requests.request("METHOD", "url") ─────────────────────
|
|
52
|
+
const REQUESTS_GENERIC_PATTERNS = compilePatterns({
|
|
53
|
+
name: 'python-requests-generic',
|
|
54
|
+
language: Python,
|
|
55
|
+
patterns: [
|
|
56
|
+
{
|
|
57
|
+
meta: {},
|
|
58
|
+
query: `
|
|
59
|
+
(call
|
|
60
|
+
function: (attribute
|
|
61
|
+
object: (identifier) @obj (#eq? @obj "requests")
|
|
62
|
+
attribute: (identifier) @method (#eq? @method "request"))
|
|
63
|
+
arguments: (argument_list . (string) @http_method (string) @path))
|
|
64
|
+
`,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
export const PYTHON_HTTP_PLUGIN = {
|
|
69
|
+
name: 'python-http',
|
|
70
|
+
language: Python,
|
|
71
|
+
scan(tree) {
|
|
72
|
+
const out = [];
|
|
73
|
+
// Providers: FastAPI
|
|
74
|
+
for (const match of runCompiledPatterns(FASTAPI_PATTERNS, tree)) {
|
|
75
|
+
const methodNode = match.captures.method;
|
|
76
|
+
const pathNode = match.captures.path;
|
|
77
|
+
if (!methodNode || !pathNode)
|
|
78
|
+
continue;
|
|
79
|
+
const httpMethod = FASTAPI_VERBS[methodNode.text];
|
|
80
|
+
if (!httpMethod)
|
|
81
|
+
continue;
|
|
82
|
+
const path = unquoteLiteral(pathNode.text);
|
|
83
|
+
if (path === null)
|
|
84
|
+
continue;
|
|
85
|
+
out.push({
|
|
86
|
+
role: 'provider',
|
|
87
|
+
framework: 'fastapi',
|
|
88
|
+
method: httpMethod,
|
|
89
|
+
path,
|
|
90
|
+
name: null,
|
|
91
|
+
confidence: 0.8,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// Consumers: requests.<verb>
|
|
95
|
+
for (const match of runCompiledPatterns(REQUESTS_VERB_PATTERNS, tree)) {
|
|
96
|
+
const methodNode = match.captures.method;
|
|
97
|
+
const pathNode = match.captures.path;
|
|
98
|
+
if (!methodNode || !pathNode)
|
|
99
|
+
continue;
|
|
100
|
+
const path = unquoteLiteral(pathNode.text);
|
|
101
|
+
if (path === null)
|
|
102
|
+
continue;
|
|
103
|
+
out.push({
|
|
104
|
+
role: 'consumer',
|
|
105
|
+
framework: 'python-requests',
|
|
106
|
+
method: methodNode.text.toUpperCase(),
|
|
107
|
+
path,
|
|
108
|
+
name: null,
|
|
109
|
+
confidence: 0.7,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// Consumers: requests.request("METHOD", "url")
|
|
113
|
+
for (const match of runCompiledPatterns(REQUESTS_GENERIC_PATTERNS, tree)) {
|
|
114
|
+
const methodNode = match.captures.http_method;
|
|
115
|
+
const pathNode = match.captures.path;
|
|
116
|
+
if (!methodNode || !pathNode)
|
|
117
|
+
continue;
|
|
118
|
+
const methodRaw = unquoteLiteral(methodNode.text);
|
|
119
|
+
const path = unquoteLiteral(pathNode.text);
|
|
120
|
+
if (methodRaw === null || path === null)
|
|
121
|
+
continue;
|
|
122
|
+
out.push({
|
|
123
|
+
role: 'consumer',
|
|
124
|
+
framework: 'python-requests',
|
|
125
|
+
method: methodRaw.toUpperCase(),
|
|
126
|
+
path,
|
|
127
|
+
name: null,
|
|
128
|
+
confidence: 0.7,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type Parser from 'tree-sitter';
|
|
2
|
+
/**
|
|
3
|
+
* Shared types for the http-route-extractor language plugins.
|
|
4
|
+
*
|
|
5
|
+
* Each plugin lives in its own file (java.ts, node.ts, ...) and owns
|
|
6
|
+
* the tree-sitter grammar import + queries. The top-level
|
|
7
|
+
* `http-route-extractor.ts` orchestrator only knows about this type
|
|
8
|
+
* module and the plugin registry (`./index.ts`). It MUST NOT import
|
|
9
|
+
* any grammar or query text directly — language-specific knowledge
|
|
10
|
+
* belongs in the plugins.
|
|
11
|
+
*/
|
|
12
|
+
export type HttpRole = 'provider' | 'consumer';
|
|
13
|
+
/**
|
|
14
|
+
* One raw HTTP detection produced by a plugin's `scan()` function. The
|
|
15
|
+
* orchestrator converts this into a full `ExtractedContract` by running
|
|
16
|
+
* path normalization and building the contract id.
|
|
17
|
+
*
|
|
18
|
+
* `path` is the raw literal string as it appeared in source (with
|
|
19
|
+
* `${...}` template placeholders still in place); the orchestrator
|
|
20
|
+
* runs the appropriate normalizer for provider vs. consumer paths.
|
|
21
|
+
*/
|
|
22
|
+
export interface HttpDetection {
|
|
23
|
+
role: HttpRole;
|
|
24
|
+
/** Short framework label, e.g. `'spring'`, `'nest'`, `'express'`. */
|
|
25
|
+
framework: string;
|
|
26
|
+
/** HTTP method in upper case (`'GET'`, `'POST'`, ...). */
|
|
27
|
+
method: string;
|
|
28
|
+
/** Raw path literal as seen in source (template placeholders intact). */
|
|
29
|
+
path: string;
|
|
30
|
+
/**
|
|
31
|
+
* Symbol name of the handler (for providers) or calling function
|
|
32
|
+
* (for consumers) when the plugin can determine it structurally.
|
|
33
|
+
* Null when no good candidate is available.
|
|
34
|
+
*/
|
|
35
|
+
name: string | null;
|
|
36
|
+
/** Confidence in (0, 1]. Source-scan plugins typically use 0.7–0.8. */
|
|
37
|
+
confidence: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* One language-scoped HTTP plugin. The plugin owns the tree-sitter
|
|
41
|
+
* grammar and the `scan` function that translates a parsed tree into
|
|
42
|
+
* zero or more `HttpDetection`s. Plugins are free to run multiple
|
|
43
|
+
* compiled pattern bundles internally (see the shared scanner's
|
|
44
|
+
* `runCompiledPatterns` helper).
|
|
45
|
+
*
|
|
46
|
+
* `language` is typed as `unknown` for the same reason as
|
|
47
|
+
* `LanguagePatterns.language` in `tree-sitter-scanner.ts` — the
|
|
48
|
+
* grammar modules export different shapes.
|
|
49
|
+
*/
|
|
50
|
+
export interface HttpLanguagePlugin {
|
|
51
|
+
/** Human-readable plugin name for diagnostics. */
|
|
52
|
+
name: string;
|
|
53
|
+
/** tree-sitter grammar object (passed to the shared parser). */
|
|
54
|
+
language: unknown;
|
|
55
|
+
/**
|
|
56
|
+
* Scan a parsed tree and return zero or more HTTP detections. Plugins
|
|
57
|
+
* must not throw — they should swallow per-match errors so a single
|
|
58
|
+
* malformed construct does not abort the whole file.
|
|
59
|
+
*/
|
|
60
|
+
scan(tree: Parser.Tree): HttpDetection[];
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import type { ContractExtractor, CypherExecutor } from '../contract-extractor.js';
|
|
2
2
|
import type { ExtractedContract, RepoHandle } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Canonicalize a provider-side HTTP path for contract-id generation:
|
|
5
|
+
* - strip query string
|
|
6
|
+
* - lower-case
|
|
7
|
+
* - drop trailing slash
|
|
8
|
+
* - collapse `:id`, `{id}`, `[id]` path params into a single `{param}`
|
|
9
|
+
*/
|
|
3
10
|
export declare function normalizeHttpPath(p: string): string;
|
|
4
11
|
export declare class HttpRouteExtractor implements ContractExtractor {
|
|
5
12
|
type: "http";
|
|
6
13
|
canExtract(_repo: RepoHandle): Promise<boolean>;
|
|
7
|
-
extract(dbExecutor: CypherExecutor | null, repoPath: string,
|
|
14
|
+
extract(dbExecutor: CypherExecutor | null, repoPath: string, _repo: RepoHandle): Promise<ExtractedContract[]>;
|
|
15
|
+
private scanFiles;
|
|
8
16
|
private extractProvidersGraph;
|
|
9
|
-
private inferMethodFromFileScan;
|
|
10
17
|
private extractProvidersSourceScan;
|
|
11
|
-
private dedupeContracts;
|
|
12
|
-
private scanSpringProviders;
|
|
13
|
-
private scanExpressProviders;
|
|
14
|
-
private scanLaravelProviders;
|
|
15
|
-
private scanFastApiProviders;
|
|
16
|
-
private makeProvider;
|
|
17
18
|
private extractConsumersGraph;
|
|
18
|
-
private inferFetchMethod;
|
|
19
19
|
private extractConsumersSourceScan;
|
|
20
|
-
private
|
|
21
|
-
private templateToPattern;
|
|
22
|
-
private scanAxiosConsumers;
|
|
23
|
-
private makeConsumer;
|
|
20
|
+
private dedupeContracts;
|
|
24
21
|
}
|