gitnexus 1.6.6-rc.75 → 1.6.6-rc.77
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/dist/core/group/extractors/http-patterns/kotlin.js +239 -9
- package/dist/core/ingestion/languages/python/captures.js +3 -0
- package/dist/core/ingestion/languages/python/depends-references.d.ts +19 -0
- package/dist/core/ingestion/languages/python/depends-references.js +65 -0
- package/dist/core/ingestion/parsing-processor.d.ts +2 -1
- package/dist/core/ingestion/parsing-processor.js +5 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +2 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +6 -0
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +2 -1
- package/dist/core/ingestion/pipeline-phases/routes.js +29 -1
- package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
- package/dist/core/ingestion/tree-sitter-queries.js +12 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +5 -0
- package/dist/core/ingestion/workers/parse-worker.js +35 -0
- package/dist/storage/parse-cache.js +1 -1
- package/package.json +1 -1
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
|
|
3
3
|
/**
|
|
4
|
-
* Kotlin HTTP plugin (Spring providers).
|
|
4
|
+
* Kotlin HTTP plugin (Spring providers + consumers).
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* **Providers** (#1849) — Spring `@RequestMapping` class prefixes and
|
|
7
|
+
* `@(Get|Post|...)Mapping` method annotations on Kotlin Spring Boot
|
|
8
|
+
* controllers. Both positional shorthand (`@GetMapping("/x")`) and
|
|
9
|
+
* named annotation arguments (`@GetMapping(value = "/x")` and
|
|
10
10
|
* `@GetMapping(path = "/x")`) are supported.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* **Consumers** (this PR) — three call-site patterns common in Kotlin
|
|
13
|
+
* Spring projects:
|
|
14
|
+
*
|
|
15
|
+
* 1. `restTemplate.getForObject("/x", ...)` and friends
|
|
16
|
+
* 2. `webClient.get().uri("/x")` (short form, 1 verb hop + 1 uri hop)
|
|
17
|
+
* 3. `Request.Builder().url("/x")` (OkHttp)
|
|
18
|
+
*
|
|
19
|
+
* The long-form `webClient.method(HttpMethod.X).uri("/y")` chain is
|
|
20
|
+
* intentionally deferred to a follow-up: it requires walk-up logic
|
|
21
|
+
* to recover the verb from a sibling `call_expression`, and we can
|
|
22
|
+
* land 80% of real-world Kotlin Spring consumer coverage with the
|
|
23
|
+
* three simpler patterns above.
|
|
16
24
|
*
|
|
17
25
|
* tree-sitter-kotlin (fwcd) AST shapes used here:
|
|
18
26
|
* class_declaration
|
|
@@ -26,6 +34,20 @@ import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-s
|
|
|
26
34
|
* string_literal
|
|
27
35
|
* type_identifier ← class name
|
|
28
36
|
*
|
|
37
|
+
* Consumer call shape (Kotlin chains everything via `navigation_expression`):
|
|
38
|
+
* call_expression ← outer `.uri("/x")` or `.url("/x")`
|
|
39
|
+
* navigation_expression
|
|
40
|
+
* call_expression ← inner `.get()` / `Request.Builder()` / `restTemplate.x`
|
|
41
|
+
* navigation_expression
|
|
42
|
+
* simple_identifier ← receiver: `webClient` / `Request` / `restTemplate`
|
|
43
|
+
* navigation_suffix ← `.method` / `.Builder` / `.getForObject`
|
|
44
|
+
* call_suffix (value_arguments)
|
|
45
|
+
* navigation_suffix ← `.uri` / `.url`
|
|
46
|
+
* call_suffix
|
|
47
|
+
* value_arguments
|
|
48
|
+
* value_argument
|
|
49
|
+
* string_literal ← the path
|
|
50
|
+
*
|
|
29
51
|
* tree-sitter-kotlin is an optional npm dependency — when its native
|
|
30
52
|
* binding is unavailable the plugin gracefully exports `null` and
|
|
31
53
|
* `http-patterns/index.ts` skips registration for `.kt`/`.kts` files.
|
|
@@ -46,6 +68,34 @@ const METHOD_ANNOTATION_TO_HTTP = {
|
|
|
46
68
|
DeleteMapping: 'DELETE',
|
|
47
69
|
PatchMapping: 'PATCH',
|
|
48
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* RestTemplate method-name → HTTP verb. Mirrors the Java plugin's
|
|
73
|
+
* `REST_TEMPLATE_TO_HTTP` (java.ts) so a polyglot repo emits the
|
|
74
|
+
* same contract IDs from .java and .kt sources.
|
|
75
|
+
*/
|
|
76
|
+
const REST_TEMPLATE_TO_HTTP = {
|
|
77
|
+
getForObject: 'GET',
|
|
78
|
+
getForEntity: 'GET',
|
|
79
|
+
postForObject: 'POST',
|
|
80
|
+
postForEntity: 'POST',
|
|
81
|
+
put: 'PUT',
|
|
82
|
+
delete: 'DELETE',
|
|
83
|
+
patchForObject: 'PATCH',
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* WebClient short-form verb → HTTP verb. The reactive WebClient API
|
|
87
|
+
* exposes `.get()`, `.post()`, `.put()`, `.delete()`, `.patch()` as
|
|
88
|
+
* one-liners that return a `RequestHeadersUriSpec` whose `.uri(...)`
|
|
89
|
+
* carries the path. We capture both pieces in a single query (see
|
|
90
|
+
* `WEB_CLIENT_SHORT_PATTERNS` below) and translate the verb here.
|
|
91
|
+
*/
|
|
92
|
+
const WEB_CLIENT_SHORT_TO_HTTP = {
|
|
93
|
+
get: 'GET',
|
|
94
|
+
post: 'POST',
|
|
95
|
+
put: 'PUT',
|
|
96
|
+
delete: 'DELETE',
|
|
97
|
+
patch: 'PATCH',
|
|
98
|
+
};
|
|
49
99
|
/**
|
|
50
100
|
* Build the plugin only if the Kotlin grammar is available. Compiling
|
|
51
101
|
* the queries against a null grammar would throw at module load time
|
|
@@ -144,6 +194,127 @@ function buildKotlinPlugin(language) {
|
|
|
144
194
|
},
|
|
145
195
|
],
|
|
146
196
|
});
|
|
197
|
+
// ─── Consumer: Spring RestTemplate ────────────────────────────────────
|
|
198
|
+
// Kotlin call-site shape mirrors the Java plugin's
|
|
199
|
+
// `REST_TEMPLATE_PATTERNS`, but goes through tree-sitter-kotlin's
|
|
200
|
+
// `navigation_expression` instead of Java's `method_invocation`:
|
|
201
|
+
//
|
|
202
|
+
// restTemplate.getForObject("/x", User::class.java)
|
|
203
|
+
//
|
|
204
|
+
// becomes
|
|
205
|
+
//
|
|
206
|
+
// call_expression
|
|
207
|
+
// navigation_expression
|
|
208
|
+
// simple_identifier "restTemplate"
|
|
209
|
+
// navigation_suffix → simple_identifier "getForObject"
|
|
210
|
+
// call_suffix
|
|
211
|
+
// value_arguments
|
|
212
|
+
// value_argument . string_literal "/x" ← captured
|
|
213
|
+
// value_argument User::class.java
|
|
214
|
+
//
|
|
215
|
+
// The receiver name is constrained to `restTemplate` (#eq? @obj),
|
|
216
|
+
// matching the Java plugin's heuristic. This means a non-conventional
|
|
217
|
+
// field name (e.g. `userServiceTemplate`) will not be picked up;
|
|
218
|
+
// that's the same trade-off already accepted on the Java side.
|
|
219
|
+
const REST_TEMPLATE_PATTERNS = compilePatterns({
|
|
220
|
+
name: 'kotlin-rest-template',
|
|
221
|
+
language,
|
|
222
|
+
patterns: [
|
|
223
|
+
{
|
|
224
|
+
meta: {},
|
|
225
|
+
query: `
|
|
226
|
+
(call_expression
|
|
227
|
+
(navigation_expression
|
|
228
|
+
(simple_identifier) @obj (#eq? @obj "restTemplate")
|
|
229
|
+
(navigation_suffix (simple_identifier) @method))
|
|
230
|
+
(call_suffix
|
|
231
|
+
(value_arguments . (value_argument . (string_literal) @path))))
|
|
232
|
+
`,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
});
|
|
236
|
+
// ─── Consumer: Spring WebClient (short form) ──────────────────────────
|
|
237
|
+
// Reactive WebClient exposes one-liner verb helpers:
|
|
238
|
+
//
|
|
239
|
+
// webClient.get().uri("/x").retrieve().awaitBody<T>()
|
|
240
|
+
// webClient.post().uri("/x")...
|
|
241
|
+
//
|
|
242
|
+
// The chain `webClient.get().uri("/x")` parses as two nested
|
|
243
|
+
// `call_expression` nodes — the OUTER call is `.uri("/x")` and the
|
|
244
|
+
// INNER call is `webClient.get()`. We anchor on the outer call and
|
|
245
|
+
// require:
|
|
246
|
+
// - inner receiver is `webClient`
|
|
247
|
+
// - inner suffix is one of the HTTP verbs (#match?)
|
|
248
|
+
// - outer suffix is exactly `uri`
|
|
249
|
+
// - outer call's first value_argument is a string literal
|
|
250
|
+
//
|
|
251
|
+
// The long-form `webClient.method(HttpMethod.GET).uri("/x")` chain
|
|
252
|
+
// uses an extra navigation hop and an enum field access — it's
|
|
253
|
+
// intentionally out of scope here (see file header).
|
|
254
|
+
const WEB_CLIENT_SHORT_PATTERNS = compilePatterns({
|
|
255
|
+
name: 'kotlin-web-client-short',
|
|
256
|
+
language,
|
|
257
|
+
patterns: [
|
|
258
|
+
{
|
|
259
|
+
meta: {},
|
|
260
|
+
query: `
|
|
261
|
+
(call_expression
|
|
262
|
+
(navigation_expression
|
|
263
|
+
(call_expression
|
|
264
|
+
(navigation_expression
|
|
265
|
+
(simple_identifier) @obj (#eq? @obj "webClient")
|
|
266
|
+
(navigation_suffix
|
|
267
|
+
(simple_identifier) @verb (#match? @verb "^(get|post|put|delete|patch)$")))
|
|
268
|
+
(call_suffix (value_arguments)))
|
|
269
|
+
(navigation_suffix (simple_identifier) @uri (#eq? @uri "uri")))
|
|
270
|
+
(call_suffix
|
|
271
|
+
(value_arguments . (value_argument . (string_literal) @path))))
|
|
272
|
+
`,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
});
|
|
276
|
+
// ─── Consumer: OkHttp Request.Builder().url("/x") ─────────────────────
|
|
277
|
+
// Kotlin parses `Request.Builder()` as a `call_expression` whose
|
|
278
|
+
// callee is a `navigation_expression` (Request → .Builder), NOT as
|
|
279
|
+
// Java's `object_creation_expression`. The chain `.url("/x")` then
|
|
280
|
+
// wraps that in another `call_expression`. The query mirrors Java's
|
|
281
|
+
// `OK_HTTP_PATTERNS` (java.ts) but adapts the node types.
|
|
282
|
+
//
|
|
283
|
+
// Receiver `Request` is constrained by name (#eq? @cls); a project
|
|
284
|
+
// that imports OkHttp's `Request` under an alias (`import okhttp3.Request as OkRequest`)
|
|
285
|
+
// would not be picked up — this matches the Java plugin's heuristic.
|
|
286
|
+
//
|
|
287
|
+
// **Known limitation — verb defaults to GET.** OkHttp encodes the
|
|
288
|
+
// verb on a *sibling* call further down the builder chain (e.g.
|
|
289
|
+
// `.post(body)` / `.get()` / `.delete()`), not on `.url(...)` itself.
|
|
290
|
+
// This query intentionally does not walk the chain to recover the
|
|
291
|
+
// verb — it emits `method: 'GET'` for every match, mirroring
|
|
292
|
+
// `java.ts:OK_HTTP_PATTERNS`. So a `Request.Builder().url("/x").post(body).build()`
|
|
293
|
+
// call becomes `http::GET::/x`, not `http::POST::/x`. This is the
|
|
294
|
+
// same trade-off Java has accepted; pinned by an anti-overreach
|
|
295
|
+
// test in `http-route-extractor.test.ts` so a future verb-walk
|
|
296
|
+
// implementation has to update this comment in lockstep.
|
|
297
|
+
const OK_HTTP_PATTERNS = compilePatterns({
|
|
298
|
+
name: 'kotlin-okhttp',
|
|
299
|
+
language,
|
|
300
|
+
patterns: [
|
|
301
|
+
{
|
|
302
|
+
meta: {},
|
|
303
|
+
query: `
|
|
304
|
+
(call_expression
|
|
305
|
+
(navigation_expression
|
|
306
|
+
(call_expression
|
|
307
|
+
(navigation_expression
|
|
308
|
+
(simple_identifier) @cls (#eq? @cls "Request")
|
|
309
|
+
(navigation_suffix (simple_identifier) @builder (#eq? @builder "Builder")))
|
|
310
|
+
(call_suffix (value_arguments)))
|
|
311
|
+
(navigation_suffix (simple_identifier) @method (#eq? @method "url")))
|
|
312
|
+
(call_suffix
|
|
313
|
+
(value_arguments . (value_argument . (string_literal) @path))))
|
|
314
|
+
`,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
});
|
|
147
318
|
/**
|
|
148
319
|
* Find the nearest enclosing class_declaration ancestor for a node, or
|
|
149
320
|
* null if the node is top-level. Mirrors the Java plugin's helper.
|
|
@@ -212,6 +383,65 @@ function buildKotlinPlugin(language) {
|
|
|
212
383
|
confidence: 0.8,
|
|
213
384
|
});
|
|
214
385
|
}
|
|
386
|
+
// ─── Consumers: RestTemplate ────────────────────────────────────
|
|
387
|
+
for (const match of runCompiledPatterns(REST_TEMPLATE_PATTERNS, tree)) {
|
|
388
|
+
const methodNode = match.captures.method;
|
|
389
|
+
const pathNode = match.captures.path;
|
|
390
|
+
if (!methodNode || !pathNode)
|
|
391
|
+
continue;
|
|
392
|
+
const httpMethod = REST_TEMPLATE_TO_HTTP[methodNode.text];
|
|
393
|
+
if (!httpMethod)
|
|
394
|
+
continue;
|
|
395
|
+
const path = unquoteLiteral(pathNode.text);
|
|
396
|
+
if (path === null)
|
|
397
|
+
continue;
|
|
398
|
+
out.push({
|
|
399
|
+
role: 'consumer',
|
|
400
|
+
framework: 'spring-rest-template',
|
|
401
|
+
method: httpMethod,
|
|
402
|
+
path,
|
|
403
|
+
name: null,
|
|
404
|
+
confidence: 0.7,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
// ─── Consumers: WebClient short form (.get()/.post()/etc → .uri) ─
|
|
408
|
+
for (const match of runCompiledPatterns(WEB_CLIENT_SHORT_PATTERNS, tree)) {
|
|
409
|
+
const verbNode = match.captures.verb;
|
|
410
|
+
const pathNode = match.captures.path;
|
|
411
|
+
if (!verbNode || !pathNode)
|
|
412
|
+
continue;
|
|
413
|
+
const httpMethod = WEB_CLIENT_SHORT_TO_HTTP[verbNode.text];
|
|
414
|
+
if (!httpMethod)
|
|
415
|
+
continue;
|
|
416
|
+
const path = unquoteLiteral(pathNode.text);
|
|
417
|
+
if (path === null)
|
|
418
|
+
continue;
|
|
419
|
+
out.push({
|
|
420
|
+
role: 'consumer',
|
|
421
|
+
framework: 'spring-web-client',
|
|
422
|
+
method: httpMethod,
|
|
423
|
+
path,
|
|
424
|
+
name: null,
|
|
425
|
+
confidence: 0.7,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
// ─── Consumers: OkHttp Request.Builder().url("path") ────────────
|
|
429
|
+
for (const match of runCompiledPatterns(OK_HTTP_PATTERNS, tree)) {
|
|
430
|
+
const pathNode = match.captures.path;
|
|
431
|
+
if (!pathNode)
|
|
432
|
+
continue;
|
|
433
|
+
const path = unquoteLiteral(pathNode.text);
|
|
434
|
+
if (path === null)
|
|
435
|
+
continue;
|
|
436
|
+
out.push({
|
|
437
|
+
role: 'consumer',
|
|
438
|
+
framework: 'okhttp',
|
|
439
|
+
method: 'GET',
|
|
440
|
+
path,
|
|
441
|
+
name: null,
|
|
442
|
+
confidence: 0.7,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
215
445
|
return out;
|
|
216
446
|
},
|
|
217
447
|
};
|
|
@@ -19,6 +19,7 @@ import { findNodeAtRange, nodeToCapture, syntheticCapture } from '../../utils/as
|
|
|
19
19
|
import { splitImportStatement } from './import-decomposer.js';
|
|
20
20
|
import { getPythonParser, getPythonScopeQuery } from './query.js';
|
|
21
21
|
import { synthesizeReceiverTypeBinding } from './receiver-binding.js';
|
|
22
|
+
import { synthesizeDependsReferences } from './depends-references.js';
|
|
22
23
|
import { computePythonArityMetadata } from './arity-metadata.js';
|
|
23
24
|
import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
|
|
24
25
|
import { getTreeSitterBufferSize } from '../../constants.js';
|
|
@@ -88,6 +89,8 @@ export function emitPythonScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
88
89
|
const synth = synthesizeReceiverTypeBinding(fnNode);
|
|
89
90
|
if (synth !== null)
|
|
90
91
|
out.push(synth);
|
|
92
|
+
for (const depRef of synthesizeDependsReferences(fnNode))
|
|
93
|
+
out.push(depRef);
|
|
91
94
|
}
|
|
92
95
|
continue;
|
|
93
96
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthesize `@reference.call.free` captures for FastAPI `Depends(callable)`
|
|
3
|
+
* parameter defaults.
|
|
4
|
+
*
|
|
5
|
+
* `Depends(get_db)` passes `get_db` as a callable that the DI framework
|
|
6
|
+
* calls on every request. The route handler is functionally a caller of
|
|
7
|
+
* the dependency — impact analysis needs that edge.
|
|
8
|
+
*
|
|
9
|
+
* Tree-sitter can't express "the first argument of a call named Depends
|
|
10
|
+
* inside a parameter default" in a single static query, so we synthesize
|
|
11
|
+
* reference captures in code, mirroring the receiver-binding pattern.
|
|
12
|
+
*/
|
|
13
|
+
import type { CaptureMatch } from '../../../../_shared/index.js';
|
|
14
|
+
import { type SyntaxNode } from '../../utils/ast-helpers.js';
|
|
15
|
+
/**
|
|
16
|
+
* Inspect a `function_definition` node's parameters for `Depends(callable)`
|
|
17
|
+
* defaults. Returns one `@reference.call.free` CaptureMatch per dependency.
|
|
18
|
+
*/
|
|
19
|
+
export declare function synthesizeDependsReferences(fnNode: SyntaxNode): readonly CaptureMatch[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthesize `@reference.call.free` captures for FastAPI `Depends(callable)`
|
|
3
|
+
* parameter defaults.
|
|
4
|
+
*
|
|
5
|
+
* `Depends(get_db)` passes `get_db` as a callable that the DI framework
|
|
6
|
+
* calls on every request. The route handler is functionally a caller of
|
|
7
|
+
* the dependency — impact analysis needs that edge.
|
|
8
|
+
*
|
|
9
|
+
* Tree-sitter can't express "the first argument of a call named Depends
|
|
10
|
+
* inside a parameter default" in a single static query, so we synthesize
|
|
11
|
+
* reference captures in code, mirroring the receiver-binding pattern.
|
|
12
|
+
*/
|
|
13
|
+
import { nodeToCapture } from '../../utils/ast-helpers.js';
|
|
14
|
+
/**
|
|
15
|
+
* Inspect a `function_definition` node's parameters for `Depends(callable)`
|
|
16
|
+
* defaults. Returns one `@reference.call.free` CaptureMatch per dependency.
|
|
17
|
+
*/
|
|
18
|
+
export function synthesizeDependsReferences(fnNode) {
|
|
19
|
+
const params = fnNode.childForFieldName('parameters');
|
|
20
|
+
if (params === null)
|
|
21
|
+
return [];
|
|
22
|
+
const results = [];
|
|
23
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
24
|
+
const param = params.namedChild(i);
|
|
25
|
+
if (param === null)
|
|
26
|
+
continue;
|
|
27
|
+
if (param.type !== 'typed_default_parameter' && param.type !== 'default_parameter') {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const defaultValue = param.childForFieldName('value') ?? param.childForFieldName('default');
|
|
31
|
+
if (defaultValue === null)
|
|
32
|
+
continue;
|
|
33
|
+
const callNode = defaultValue.type === 'call' ? defaultValue : null;
|
|
34
|
+
if (callNode === null)
|
|
35
|
+
continue;
|
|
36
|
+
const fnIdent = callNode.childForFieldName('function');
|
|
37
|
+
if (fnIdent === null || fnIdent.type !== 'identifier' || fnIdent.text !== 'Depends')
|
|
38
|
+
continue;
|
|
39
|
+
const args = callNode.childForFieldName('arguments');
|
|
40
|
+
if (args === null || args.namedChildCount === 0)
|
|
41
|
+
continue;
|
|
42
|
+
const firstArg = args.namedChild(0);
|
|
43
|
+
if (firstArg === null)
|
|
44
|
+
continue;
|
|
45
|
+
if (firstArg.type === 'identifier') {
|
|
46
|
+
results.push({
|
|
47
|
+
'@reference.call.free': nodeToCapture('@reference.call.free', firstArg),
|
|
48
|
+
'@reference.name': nodeToCapture('@reference.name', firstArg),
|
|
49
|
+
});
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (firstArg.type === 'attribute') {
|
|
53
|
+
const attrName = firstArg.childForFieldName('attribute');
|
|
54
|
+
const obj = firstArg.childForFieldName('object');
|
|
55
|
+
if (attrName !== null && obj !== null) {
|
|
56
|
+
results.push({
|
|
57
|
+
'@reference.call.member': nodeToCapture('@reference.call.member', attrName),
|
|
58
|
+
'@reference.name': nodeToCapture('@reference.name', attrName),
|
|
59
|
+
'@reference.receiver': nodeToCapture('@reference.receiver', obj),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
@@ -3,7 +3,7 @@ import type { SymbolTableWriter, ExtractedHeritage } from './model/index.js';
|
|
|
3
3
|
import { ASTCache } from './ast-cache.js';
|
|
4
4
|
import type { ParsedFile } from '../../_shared/index.js';
|
|
5
5
|
import { WorkerPool } from './workers/worker-pool.js';
|
|
6
|
-
import type { ParseWorkerResult, ExtractedImport, ExtractedCall, ExtractedAssignment, ExtractedRoute, ExtractedFetchCall, ExtractedDecoratorRoute, ExtractedToolDef, FileConstructorBindings, FileScopeBindings, ExtractedORMQuery } from './workers/parse-worker.js';
|
|
6
|
+
import type { ParseWorkerResult, ExtractedImport, ExtractedCall, ExtractedAssignment, ExtractedRoute, ExtractedFetchCall, ExtractedDecoratorRoute, ExtractedToolDef, FileConstructorBindings, FileScopeBindings, ExtractedORMQuery, FetchWrapperDef } from './workers/parse-worker.js';
|
|
7
7
|
export type FileProgressCallback = (current: number, total: number, filePath: string) => void;
|
|
8
8
|
export interface WorkerExtractedData {
|
|
9
9
|
imports: ExtractedImport[];
|
|
@@ -12,6 +12,7 @@ export interface WorkerExtractedData {
|
|
|
12
12
|
heritage: ExtractedHeritage[];
|
|
13
13
|
routes: ExtractedRoute[];
|
|
14
14
|
fetchCalls: ExtractedFetchCall[];
|
|
15
|
+
fetchWrapperDefs: FetchWrapperDef[];
|
|
15
16
|
decoratorRoutes: ExtractedDecoratorRoute[];
|
|
16
17
|
toolDefs: ExtractedToolDef[];
|
|
17
18
|
ormQueries: ExtractedORMQuery[];
|
|
@@ -35,6 +35,7 @@ export const mergeChunkResults = (graph, symbolTable, chunkResults) => {
|
|
|
35
35
|
const allHeritage = [];
|
|
36
36
|
const allRoutes = [];
|
|
37
37
|
const allFetchCalls = [];
|
|
38
|
+
const allFetchWrapperDefs = [];
|
|
38
39
|
const allDecoratorRoutes = [];
|
|
39
40
|
const allToolDefs = [];
|
|
40
41
|
const allORMQueries = [];
|
|
@@ -77,6 +78,8 @@ export const mergeChunkResults = (graph, symbolTable, chunkResults) => {
|
|
|
77
78
|
allRoutes.push(item);
|
|
78
79
|
for (const item of result.fetchCalls)
|
|
79
80
|
allFetchCalls.push(item);
|
|
81
|
+
for (const item of result.fetchWrapperDefs ?? [])
|
|
82
|
+
allFetchWrapperDefs.push(item);
|
|
80
83
|
for (const item of result.decoratorRoutes)
|
|
81
84
|
allDecoratorRoutes.push(item);
|
|
82
85
|
for (const item of result.toolDefs)
|
|
@@ -100,6 +103,7 @@ export const mergeChunkResults = (graph, symbolTable, chunkResults) => {
|
|
|
100
103
|
heritage: allHeritage,
|
|
101
104
|
routes: allRoutes,
|
|
102
105
|
fetchCalls: allFetchCalls,
|
|
106
|
+
fetchWrapperDefs: allFetchWrapperDefs,
|
|
103
107
|
decoratorRoutes: allDecoratorRoutes,
|
|
104
108
|
toolDefs: allToolDefs,
|
|
105
109
|
ormQueries: allORMQueries,
|
|
@@ -132,6 +136,7 @@ outRawResults) => {
|
|
|
132
136
|
heritage: [],
|
|
133
137
|
routes: [],
|
|
134
138
|
fetchCalls: [],
|
|
139
|
+
fetchWrapperDefs: [],
|
|
135
140
|
decoratorRoutes: [],
|
|
136
141
|
toolDefs: [],
|
|
137
142
|
ormQueries: [],
|
|
@@ -16,7 +16,7 @@ import { type ExportedTypeMap } from '../call-processor.js';
|
|
|
16
16
|
import { createResolutionContext } from '../model/resolution-context.js';
|
|
17
17
|
import { ASTCache } from '../ast-cache.js';
|
|
18
18
|
import { type PipelineProgress } from '../../../_shared/index.js';
|
|
19
|
-
import type { ExtractedDecoratorRoute, ExtractedFetchCall, ExtractedORMQuery, ExtractedRoute, ExtractedToolDef } from '../workers/parse-worker.js';
|
|
19
|
+
import type { ExtractedDecoratorRoute, ExtractedFetchCall, ExtractedORMQuery, ExtractedRoute, ExtractedToolDef, FetchWrapperDef } from '../workers/parse-worker.js';
|
|
20
20
|
import type { KnowledgeGraph } from '../../graph/types.js';
|
|
21
21
|
import type { PipelineOptions } from '../pipeline.js';
|
|
22
22
|
type ScannedFile = {
|
|
@@ -38,6 +38,7 @@ type ProgressFn = (progress: PipelineProgress) => void;
|
|
|
38
38
|
export declare function runChunkedParseAndResolve(graph: KnowledgeGraph, scannedFiles: ScannedFile[], allPaths: string[], totalFiles: number, repoPath: string, pipelineStart: number, onProgress: ProgressFn, options?: PipelineOptions): Promise<{
|
|
39
39
|
exportedTypeMap: ExportedTypeMap;
|
|
40
40
|
allFetchCalls: ExtractedFetchCall[];
|
|
41
|
+
allFetchWrapperDefs: FetchWrapperDef[];
|
|
41
42
|
allExtractedRoutes: ExtractedRoute[];
|
|
42
43
|
allDecoratorRoutes: ExtractedDecoratorRoute[];
|
|
43
44
|
allToolDefs: ExtractedToolDef[];
|
|
@@ -234,6 +234,7 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
|
|
|
234
234
|
// it, and later wildcard chunks re-run it themselves.
|
|
235
235
|
let hasSynthesized = false;
|
|
236
236
|
const allFetchCalls = [];
|
|
237
|
+
const allFetchWrapperDefs = [];
|
|
237
238
|
const allExtractedRoutes = [];
|
|
238
239
|
const allDecoratorRoutes = [];
|
|
239
240
|
const allToolDefs = [];
|
|
@@ -530,6 +531,10 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
|
|
|
530
531
|
for (const item of chunkWorkerData.fetchCalls)
|
|
531
532
|
allFetchCalls.push(item);
|
|
532
533
|
}
|
|
534
|
+
if (chunkWorkerData.fetchWrapperDefs?.length) {
|
|
535
|
+
for (const item of chunkWorkerData.fetchWrapperDefs)
|
|
536
|
+
allFetchWrapperDefs.push(item);
|
|
537
|
+
}
|
|
533
538
|
if (chunkWorkerData.routes?.length) {
|
|
534
539
|
for (const item of chunkWorkerData.routes)
|
|
535
540
|
allExtractedRoutes.push(item);
|
|
@@ -876,6 +881,7 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
|
|
|
876
881
|
return {
|
|
877
882
|
exportedTypeMap,
|
|
878
883
|
allFetchCalls,
|
|
884
|
+
allFetchWrapperDefs,
|
|
879
885
|
allExtractedRoutes,
|
|
880
886
|
allDecoratorRoutes,
|
|
881
887
|
allToolDefs,
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import type { PipelinePhase } from './types.js';
|
|
19
19
|
import type { BindingAccumulator } from '../binding-accumulator.js';
|
|
20
20
|
import type { ParsedFile } from '../../../_shared/index.js';
|
|
21
|
-
import type { ExtractedFetchCall, ExtractedRoute, ExtractedDecoratorRoute, ExtractedToolDef, ExtractedORMQuery } from '../workers/parse-worker.js';
|
|
21
|
+
import type { ExtractedFetchCall, ExtractedRoute, ExtractedDecoratorRoute, ExtractedToolDef, ExtractedORMQuery, FetchWrapperDef } from '../workers/parse-worker.js';
|
|
22
22
|
import type { createResolutionContext } from '../model/resolution-context.js';
|
|
23
23
|
import type { ASTCache } from '../ast-cache.js';
|
|
24
24
|
export interface ParseOutput {
|
|
@@ -34,6 +34,7 @@ export interface ParseOutput {
|
|
|
34
34
|
*/
|
|
35
35
|
readonly exportedTypeMap: ReadonlyMap<string, ReadonlyMap<string, string>>;
|
|
36
36
|
readonly allFetchCalls: readonly ExtractedFetchCall[];
|
|
37
|
+
readonly allFetchWrapperDefs: readonly FetchWrapperDef[];
|
|
37
38
|
readonly allExtractedRoutes: readonly ExtractedRoute[];
|
|
38
39
|
readonly allDecoratorRoutes: readonly ExtractedDecoratorRoute[];
|
|
39
40
|
readonly allToolDefs: readonly ExtractedToolDef[];
|
|
@@ -94,11 +94,14 @@ export function normalizeExtractedRoutePath(routePath, prefix) {
|
|
|
94
94
|
const joined = prefixPart ? `/${prefixPart}${pathPart ? `/${pathPart}` : ''}` : `/${pathPart}`;
|
|
95
95
|
return joined.replace(/\/+/g, '/') || '/';
|
|
96
96
|
}
|
|
97
|
+
function escapeRegex(s) {
|
|
98
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
99
|
+
}
|
|
97
100
|
export const routesPhase = {
|
|
98
101
|
name: 'routes',
|
|
99
102
|
deps: ['parse'],
|
|
100
103
|
async execute(ctx, deps) {
|
|
101
|
-
const { allPaths, allFetchCalls: parseFetchCalls, allExtractedRoutes, allDecoratorRoutes, } = getPhaseOutput(deps, 'parse');
|
|
104
|
+
const { allPaths, allFetchCalls: parseFetchCalls, allFetchWrapperDefs, allExtractedRoutes, allDecoratorRoutes, } = getPhaseOutput(deps, 'parse');
|
|
102
105
|
// Local copy — routes phase must not mutate upstream ParseOutput
|
|
103
106
|
const allFetchCalls = [...parseFetchCalls];
|
|
104
107
|
const routeRegistry = new Map();
|
|
@@ -289,6 +292,31 @@ export const routesPhase = {
|
|
|
289
292
|
}
|
|
290
293
|
}
|
|
291
294
|
}
|
|
295
|
+
// ── Cross-file fetch wrapper consumer extraction ──
|
|
296
|
+
// When the parse phase discovered functions that internally call fetch(),
|
|
297
|
+
// scan JS/TS consumer files for calls to those wrapper functions with
|
|
298
|
+
// URL-like string arguments and add them to allFetchCalls so
|
|
299
|
+
// processNextjsFetchRoutes can create FETCHES edges.
|
|
300
|
+
if (allFetchWrapperDefs && allFetchWrapperDefs.length > 0 && routeRegistry.size > 0) {
|
|
301
|
+
const wrapperNames = new Set(allFetchWrapperDefs.map((d) => d.functionName));
|
|
302
|
+
const jsFiles = allPaths.filter((p) => /\.[jt]sx?$/.test(p));
|
|
303
|
+
if (jsFiles.length > 0 && wrapperNames.size > 0) {
|
|
304
|
+
const jsContents = await readFileContents(ctx.repoPath, jsFiles);
|
|
305
|
+
for (const [filePath, content] of jsContents) {
|
|
306
|
+
for (const name of wrapperNames) {
|
|
307
|
+
const regex = new RegExp(`\\b${escapeRegex(name)}\\s*\\(\\s*['"\`](/[^'"\`\\s)]+)['"\`]`, 'g');
|
|
308
|
+
let match;
|
|
309
|
+
while ((match = regex.exec(content)) !== null) {
|
|
310
|
+
allFetchCalls.push({
|
|
311
|
+
filePath,
|
|
312
|
+
fetchURL: match[1],
|
|
313
|
+
lineNumber: content.substring(0, match.index).split('\n').length,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
292
320
|
if (routeRegistry.size > 0 && allFetchCalls.length > 0) {
|
|
293
321
|
const routeURLToFile = new Map();
|
|
294
322
|
for (const [url, entry] of routeRegistry)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare const TYPESCRIPT_QUERIES = "\n(class_declaration\n name: (type_identifier) @name) @definition.class\n\n(abstract_class_declaration\n name: (type_identifier) @name) @definition.class\n\n(interface_declaration\n name: (type_identifier) @name) @definition.interface\n\n(function_declaration\n name: (identifier) @name) @definition.function\n\n; TypeScript overload signatures (function_signature is a separate node type from function_declaration)\n(function_signature\n name: (identifier) @name) @definition.function\n\n(method_definition\n name: (property_identifier) @name) @definition.method\n\n; ES2022 #private methods (private_property_identifier not matched by property_identifier)\n(method_definition\n name: (private_property_identifier) @name) @definition.method\n\n; Abstract method signatures in abstract classes\n(abstract_method_signature\n name: (property_identifier) @name) @definition.method\n\n; Interface method signatures\n(method_signature\n name: (property_identifier) @name) @definition.method\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function))) @definition.function\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function)))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression)))) @definition.function\n\n; Object-property arrows / function expressions: `{ addItem: () => ... }`.\n; The pair's key field carries the meaningful name. Without these patterns,\n; calls inside the arrow are attributed to the file (issue #1166), and the\n; arrow itself is invisible to context() / impact() despite carrying real\n; behaviour (Zustand actions, TanStack queryFn, React Context providers).\n; String-key variant covers `\"add-item\": () => ...`; computed keys\n; (`[K]: () => ...`) intentionally fall through anonymous.\n(pair\n key: (property_identifier) @name\n value: (arrow_function)) @definition.function\n\n(pair\n key: (property_identifier) @name\n value: (function_expression)) @definition.function\n\n(pair\n key: (string (string_fragment) @name)\n value: (arrow_function)) @definition.function\n\n(pair\n key: (string (string_fragment) @name)\n value: (function_expression)) @definition.function\n\n; HOC-wrapped variable declarations: `const X = HOC((args) => { ... })`.\n; Mirrors the registry-primary patterns in `languages/typescript/query.ts`\n; so the legacy Call-Resolution DAG and the registry-primary pipeline\n; produce the same set of `Function` nodes \u2014 required for the CI parity\n; gate. Covers React.forwardRef / memo / useCallback / useMemo / observer\n; / debounce / user-defined HOC factories. The `var X = HOC(...)` form is\n; mirrored too (registry-primary has it) so that codebases mixing `var` and\n; `const` see identical attribution on both pipelines. See\n; `tsExtractFunctionName` for the resolution logic and the `query.ts`\n; comment for the full anchor-discipline rationale and the chained-\n; array-method trade-off.\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function))))) @definition.function\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression))))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function)))))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression)))))) @definition.function\n\n; `var X = HOC(...)` parity with registry-primary. Legacy code (and any\n; transpiler output that downlevels `const` to `var`) hits this shape.\n(variable_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function))))) @definition.function\n\n(variable_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression))))) @definition.function\n\n; Variable/constant declarations (non-function values).\n; Overlap with @definition.function patterns is handled by parse-worker dedup.\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name)) @definition.const\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name))) @definition.const\n\n; var declarations (mutable, function-scoped)\n(variable_declaration\n (variable_declarator\n name: (identifier) @name)) @definition.variable\n\n(import_statement\n source: (string) @import.source) @import\n\n; Re-export statements: export { X } from './y'\n(export_statement\n source: (string) @import.source) @import\n\n(call_expression\n function: (identifier) @call.name) @call\n\n(call_expression\n function: (member_expression\n property: (property_identifier) @call.name)) @call\n\n; Generic awaited free call: await fn<T>(args)\n; tree-sitter-typescript parses \"await fn<T>(args)\" as a call_expression whose\n; \"function\" field is an await_expression (not a bare identifier), because the\n; grammar resolves the ambiguity between generics and comparisons by consuming\n; \"await fn\" as an expression before attaching <T> as type_arguments.\n(call_expression\n function: (await_expression\n (identifier) @call.name)\n (type_arguments)) @call\n\n; Generic awaited member call: await obj.fn<T>(args)\n(call_expression\n function: (await_expression\n (member_expression\n property: (property_identifier) @call.name))\n (type_arguments)) @call\n\n; Constructor calls: new Foo()\n(new_expression\n constructor: (identifier) @call.name) @call\n\n; Class properties \u2014 public_field_definition covers most TS class fields\n(public_field_definition\n name: (property_identifier) @name) @definition.property\n\n; Private class fields: #address: Address\n(public_field_definition\n name: (private_property_identifier) @name) @definition.property\n\n; Constructor parameter properties: constructor(public address: Address)\n(required_parameter\n (accessibility_modifier)\n pattern: (identifier) @name) @definition.property\n\n; Heritage queries - class extends\n(class_declaration\n name: (type_identifier) @heritage.class\n (class_heritage\n (extends_clause\n value: (identifier) @heritage.extends))) @heritage\n\n; Heritage queries - class implements interface\n(class_declaration\n name: (type_identifier) @heritage.class\n (class_heritage\n (implements_clause\n (type_identifier) @heritage.implements))) @heritage.impl\n\n; Write access: obj.field = value\n(assignment_expression\n left: (member_expression\n object: (_) @assignment.receiver\n property: (property_identifier) @assignment.property)\n right: (_)) @assignment\n\n; Write access: obj.field += value (compound assignment)\n(augmented_assignment_expression\n left: (member_expression\n object: (_) @assignment.receiver\n property: (property_identifier) @assignment.property)\n right: (_)) @assignment\n\n; HTTP consumers: fetch('/path'), axios.get('/path'), $.get('/path'), etc.\n; fetch() \u2014 global function\n(call_expression\n function: (identifier) @_fetch_fn (#eq? @_fetch_fn \"fetch\")\n arguments: (arguments\n [(string (string_fragment) @route.url)\n (template_string) @route.template_url])) @route.fetch\n\n; axios.get/post/put/delete/patch('/path'), $.get/post/ajax({url:'/path'})\n(call_expression\n function: (member_expression\n property: (property_identifier) @http_client.method)\n arguments: (arguments\n (string (string_fragment) @http_client.url))) @http_client\n\n; Decorators: @Controller, @Get, @Post, etc.\n(decorator\n (call_expression\n function: (identifier) @decorator.name\n arguments: (arguments (string (string_fragment) @decorator.arg)?))) @decorator\n\n; Express/Hono route registration: app.get('/path', handler), router.post('/path', fn)\n(call_expression\n function: (member_expression\n property: (property_identifier) @express_route.method)\n arguments: (arguments\n (string (string_fragment) @express_route.path))) @express_route\n";
|
|
2
|
-
export declare const JAVASCRIPT_QUERIES = "\n(class_declaration\n name: (identifier) @name) @definition.class\n\n(function_declaration\n name: (identifier) @name) @definition.function\n\n(method_definition\n name: (property_identifier) @name) @definition.method\n\n; ES2022 #private methods\n(method_definition\n name: (private_property_identifier) @name) @definition.method\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function))) @definition.function\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function)))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression)))) @definition.function\n\n; Object-property arrows / function expressions: `{ addItem: () => ... }`.\n; See TYPESCRIPT_QUERIES for rationale (issue #1166).\n(pair\n key: (property_identifier) @name\n value: (arrow_function)) @definition.function\n\n(pair\n key: (property_identifier) @name\n value: (function_expression)) @definition.function\n\n(pair\n key: (string (string_fragment) @name)\n value: (arrow_function)) @definition.function\n\n(pair\n key: (string (string_fragment) @name)\n value: (function_expression)) @definition.function\n\n; HOC-wrapped variable declarations: `const X = HOC((args) => { ... })`.\n; See TYPESCRIPT_QUERIES section above for the full rationale (issue #1166\n; follow-up \u2014 covers forwardRef / memo / useCallback / useMemo / observer\n; / debounce / user-defined HOC factories). Both `const` and `var` forms\n; are mirrored so JS code that uses `var` (or transpiler output) gets the\n; same attribution as the registry-primary path.\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function))))) @definition.function\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression))))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function)))))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression)))))) @definition.function\n\n; `var X = HOC(...)` parity with registry-primary.\n(variable_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function))))) @definition.function\n\n(variable_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression))))) @definition.function\n\n; Variable/constant declarations (non-function values).\n; Overlap with @definition.function patterns is handled by parse-worker dedup.\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name)) @definition.const\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name))) @definition.const\n\n; var declarations (mutable, function-scoped)\n(variable_declaration\n (variable_declarator\n name: (identifier) @name)) @definition.variable\n\n(import_statement\n source: (string) @import.source) @import\n\n; Re-export statements: export { X } from './y'\n(export_statement\n source: (string) @import.source) @import\n\n(call_expression\n function: (identifier) @call.name) @call\n\n(call_expression\n function: (member_expression\n property: (property_identifier) @call.name)) @call\n\n; Constructor calls: new Foo()\n(new_expression\n constructor: (identifier) @call.name) @call\n\n; Class fields \u2014 field_definition captures JS class fields (class User { address = ... })\n(field_definition\n property: (property_identifier) @name) @definition.property\n\n; Heritage queries - class extends (JavaScript uses different AST than TypeScript)\n; In tree-sitter-javascript, class_heritage directly contains the parent identifier\n(class_declaration\n name: (identifier) @heritage.class\n (class_heritage\n (identifier) @heritage.extends)) @heritage\n\n; Write access: obj.field = value\n(assignment_expression\n left: (member_expression\n object: (_) @assignment.receiver\n property: (property_identifier) @assignment.property)\n right: (_)) @assignment\n\n; Write access: obj.field += value (compound assignment)\n(augmented_assignment_expression\n left: (member_expression\n object: (_) @assignment.receiver\n property: (property_identifier) @assignment.property)\n right: (_)) @assignment\n\n; HTTP consumers: fetch('/path'), axios.get('/path'), $.get('/path'), etc.\n(call_expression\n function: (identifier) @_fetch_fn (#eq? @_fetch_fn \"fetch\")\n arguments: (arguments\n [(string (string_fragment) @route.url)\n (template_string) @route.template_url])) @route.fetch\n\n; axios.get/post, $.get/post/ajax\n(call_expression\n function: (member_expression\n property: (property_identifier) @http_client.method)\n arguments: (arguments\n (string (string_fragment) @http_client.url))) @http_client\n\n; Express/Hono route registration\n(call_expression\n function: (member_expression\n property: (property_identifier) @express_route.method)\n arguments: (arguments\n (string (string_fragment) @express_route.path))) @express_route\n";
|
|
1
|
+
export declare const TYPESCRIPT_QUERIES = "\n(class_declaration\n name: (type_identifier) @name) @definition.class\n\n(abstract_class_declaration\n name: (type_identifier) @name) @definition.class\n\n(interface_declaration\n name: (type_identifier) @name) @definition.interface\n\n(function_declaration\n name: (identifier) @name) @definition.function\n\n; TypeScript overload signatures (function_signature is a separate node type from function_declaration)\n(function_signature\n name: (identifier) @name) @definition.function\n\n(method_definition\n name: (property_identifier) @name) @definition.method\n\n; ES2022 #private methods (private_property_identifier not matched by property_identifier)\n(method_definition\n name: (private_property_identifier) @name) @definition.method\n\n; Abstract method signatures in abstract classes\n(abstract_method_signature\n name: (property_identifier) @name) @definition.method\n\n; Interface method signatures\n(method_signature\n name: (property_identifier) @name) @definition.method\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function))) @definition.function\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function)))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression)))) @definition.function\n\n; Object-property arrows / function expressions: `{ addItem: () => ... }`.\n; The pair's key field carries the meaningful name. Without these patterns,\n; calls inside the arrow are attributed to the file (issue #1166), and the\n; arrow itself is invisible to context() / impact() despite carrying real\n; behaviour (Zustand actions, TanStack queryFn, React Context providers).\n; String-key variant covers `\"add-item\": () => ...`; computed keys\n; (`[K]: () => ...`) intentionally fall through anonymous.\n(pair\n key: (property_identifier) @name\n value: (arrow_function)) @definition.function\n\n(pair\n key: (property_identifier) @name\n value: (function_expression)) @definition.function\n\n(pair\n key: (string (string_fragment) @name)\n value: (arrow_function)) @definition.function\n\n(pair\n key: (string (string_fragment) @name)\n value: (function_expression)) @definition.function\n\n; HOC-wrapped variable declarations: `const X = HOC((args) => { ... })`.\n; Mirrors the registry-primary patterns in `languages/typescript/query.ts`\n; so the legacy Call-Resolution DAG and the registry-primary pipeline\n; produce the same set of `Function` nodes \u2014 required for the CI parity\n; gate. Covers React.forwardRef / memo / useCallback / useMemo / observer\n; / debounce / user-defined HOC factories. The `var X = HOC(...)` form is\n; mirrored too (registry-primary has it) so that codebases mixing `var` and\n; `const` see identical attribution on both pipelines. See\n; `tsExtractFunctionName` for the resolution logic and the `query.ts`\n; comment for the full anchor-discipline rationale and the chained-\n; array-method trade-off.\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function))))) @definition.function\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression))))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function)))))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression)))))) @definition.function\n\n; `var X = HOC(...)` parity with registry-primary. Legacy code (and any\n; transpiler output that downlevels `const` to `var`) hits this shape.\n(variable_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function))))) @definition.function\n\n(variable_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression))))) @definition.function\n\n; Variable/constant declarations (non-function values).\n; Overlap with @definition.function patterns is handled by parse-worker dedup.\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name)) @definition.const\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name))) @definition.const\n\n; var declarations (mutable, function-scoped)\n(variable_declaration\n (variable_declarator\n name: (identifier) @name)) @definition.variable\n\n(import_statement\n source: (string) @import.source) @import\n\n; Re-export statements: export { X } from './y'\n(export_statement\n source: (string) @import.source) @import\n\n(call_expression\n function: (identifier) @call.name) @call\n\n(call_expression\n function: (member_expression\n property: (property_identifier) @call.name)) @call\n\n; Generic awaited free call: await fn<T>(args)\n; tree-sitter-typescript parses \"await fn<T>(args)\" as a call_expression whose\n; \"function\" field is an await_expression (not a bare identifier), because the\n; grammar resolves the ambiguity between generics and comparisons by consuming\n; \"await fn\" as an expression before attaching <T> as type_arguments.\n(call_expression\n function: (await_expression\n (identifier) @call.name)\n (type_arguments)) @call\n\n; Generic awaited member call: await obj.fn<T>(args)\n(call_expression\n function: (await_expression\n (member_expression\n property: (property_identifier) @call.name))\n (type_arguments)) @call\n\n; Constructor calls: new Foo()\n(new_expression\n constructor: (identifier) @call.name) @call\n\n; Class properties \u2014 public_field_definition covers most TS class fields\n(public_field_definition\n name: (property_identifier) @name) @definition.property\n\n; Private class fields: #address: Address\n(public_field_definition\n name: (private_property_identifier) @name) @definition.property\n\n; Constructor parameter properties: constructor(public address: Address)\n(required_parameter\n (accessibility_modifier)\n pattern: (identifier) @name) @definition.property\n\n; Heritage queries - class extends\n(class_declaration\n name: (type_identifier) @heritage.class\n (class_heritage\n (extends_clause\n value: (identifier) @heritage.extends))) @heritage\n\n; Heritage queries - class implements interface\n(class_declaration\n name: (type_identifier) @heritage.class\n (class_heritage\n (implements_clause\n (type_identifier) @heritage.implements))) @heritage.impl\n\n; Write access: obj.field = value\n(assignment_expression\n left: (member_expression\n object: (_) @assignment.receiver\n property: (property_identifier) @assignment.property)\n right: (_)) @assignment\n\n; Write access: obj.field += value (compound assignment)\n(augmented_assignment_expression\n left: (member_expression\n object: (_) @assignment.receiver\n property: (property_identifier) @assignment.property)\n right: (_)) @assignment\n\n; HTTP consumers: fetch('/path'), axios.get('/path'), $.get('/path'), etc.\n; fetch() \u2014 global function\n(call_expression\n function: (identifier) @_fetch_fn (#eq? @_fetch_fn \"fetch\")\n arguments: (arguments\n [(string (string_fragment) @route.url)\n (template_string) @route.template_url])) @route.fetch\n\n; Custom fetch wrappers: apiFetch('/path'), fetchJSON('/api/data'), httpGet('/users'), etc.\n(call_expression\n function: (identifier) @_wrapper_fn (#match? @_wrapper_fn \"^(api(Fetch|Get|Post|Put|Delete|Patch|Request)|fetch(API|JSON|Data|Endpoint|Resource|Url)|http(Fetch|Get|Post|Put|Delete|Patch|Request))$\")\n arguments: (arguments\n (string (string_fragment) @route.url))) @route.fetch\n\n; axios.get/post/put/delete/patch('/path'), $.get/post/ajax({url:'/path'})\n(call_expression\n function: (member_expression\n property: (property_identifier) @http_client.method)\n arguments: (arguments\n (string (string_fragment) @http_client.url))) @http_client\n\n; Decorators: @Controller, @Get, @Post, etc.\n(decorator\n (call_expression\n function: (identifier) @decorator.name\n arguments: (arguments (string (string_fragment) @decorator.arg)?))) @decorator\n\n; Express/Hono route registration: app.get('/path', handler), router.post('/path', fn)\n(call_expression\n function: (member_expression\n property: (property_identifier) @express_route.method)\n arguments: (arguments\n (string (string_fragment) @express_route.path))) @express_route\n";
|
|
2
|
+
export declare const JAVASCRIPT_QUERIES = "\n(class_declaration\n name: (identifier) @name) @definition.class\n\n(function_declaration\n name: (identifier) @name) @definition.function\n\n(method_definition\n name: (property_identifier) @name) @definition.method\n\n; ES2022 #private methods\n(method_definition\n name: (private_property_identifier) @name) @definition.method\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function))) @definition.function\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (arrow_function)))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (function_expression)))) @definition.function\n\n; Object-property arrows / function expressions: `{ addItem: () => ... }`.\n; See TYPESCRIPT_QUERIES for rationale (issue #1166).\n(pair\n key: (property_identifier) @name\n value: (arrow_function)) @definition.function\n\n(pair\n key: (property_identifier) @name\n value: (function_expression)) @definition.function\n\n(pair\n key: (string (string_fragment) @name)\n value: (arrow_function)) @definition.function\n\n(pair\n key: (string (string_fragment) @name)\n value: (function_expression)) @definition.function\n\n; HOC-wrapped variable declarations: `const X = HOC((args) => { ... })`.\n; See TYPESCRIPT_QUERIES section above for the full rationale (issue #1166\n; follow-up \u2014 covers forwardRef / memo / useCallback / useMemo / observer\n; / debounce / user-defined HOC factories). Both `const` and `var` forms\n; are mirrored so JS code that uses `var` (or transpiler output) gets the\n; same attribution as the registry-primary path.\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function))))) @definition.function\n\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression))))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function)))))) @definition.function\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression)))))) @definition.function\n\n; `var X = HOC(...)` parity with registry-primary.\n(variable_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (arrow_function))))) @definition.function\n\n(variable_declaration\n (variable_declarator\n name: (identifier) @name\n value: (call_expression\n arguments: (arguments\n (function_expression))))) @definition.function\n\n; Variable/constant declarations (non-function values).\n; Overlap with @definition.function patterns is handled by parse-worker dedup.\n(lexical_declaration\n (variable_declarator\n name: (identifier) @name)) @definition.const\n\n(export_statement\n declaration: (lexical_declaration\n (variable_declarator\n name: (identifier) @name))) @definition.const\n\n; var declarations (mutable, function-scoped)\n(variable_declaration\n (variable_declarator\n name: (identifier) @name)) @definition.variable\n\n(import_statement\n source: (string) @import.source) @import\n\n; Re-export statements: export { X } from './y'\n(export_statement\n source: (string) @import.source) @import\n\n(call_expression\n function: (identifier) @call.name) @call\n\n(call_expression\n function: (member_expression\n property: (property_identifier) @call.name)) @call\n\n; Constructor calls: new Foo()\n(new_expression\n constructor: (identifier) @call.name) @call\n\n; Class fields \u2014 field_definition captures JS class fields (class User { address = ... })\n(field_definition\n property: (property_identifier) @name) @definition.property\n\n; Heritage queries - class extends (JavaScript uses different AST than TypeScript)\n; In tree-sitter-javascript, class_heritage directly contains the parent identifier\n(class_declaration\n name: (identifier) @heritage.class\n (class_heritage\n (identifier) @heritage.extends)) @heritage\n\n; Write access: obj.field = value\n(assignment_expression\n left: (member_expression\n object: (_) @assignment.receiver\n property: (property_identifier) @assignment.property)\n right: (_)) @assignment\n\n; Write access: obj.field += value (compound assignment)\n(augmented_assignment_expression\n left: (member_expression\n object: (_) @assignment.receiver\n property: (property_identifier) @assignment.property)\n right: (_)) @assignment\n\n; HTTP consumers: fetch('/path'), axios.get('/path'), $.get('/path'), etc.\n(call_expression\n function: (identifier) @_fetch_fn (#eq? @_fetch_fn \"fetch\")\n arguments: (arguments\n [(string (string_fragment) @route.url)\n (template_string) @route.template_url])) @route.fetch\n\n; Custom fetch wrappers: apiFetch('/path'), fetchJSON('/api/data'), httpGet('/users'), etc.\n(call_expression\n function: (identifier) @_wrapper_fn (#match? @_wrapper_fn \"^(api(Fetch|Get|Post|Put|Delete|Patch|Request)|fetch(API|JSON|Data|Endpoint|Resource|Url)|http(Fetch|Get|Post|Put|Delete|Patch|Request))$\")\n arguments: (arguments\n (string (string_fragment) @route.url))) @route.fetch\n\n; axios.get/post, $.get/post/ajax\n(call_expression\n function: (member_expression\n property: (property_identifier) @http_client.method)\n arguments: (arguments\n (string (string_fragment) @http_client.url))) @http_client\n\n; Express/Hono route registration\n(call_expression\n function: (member_expression\n property: (property_identifier) @express_route.method)\n arguments: (arguments\n (string (string_fragment) @express_route.path))) @express_route\n";
|
|
3
3
|
export declare const PYTHON_QUERIES = "\n(class_definition\n name: (identifier) @name) @definition.class\n\n(function_definition\n name: (identifier) @name) @definition.function\n\n(import_statement\n name: (dotted_name) @import.source) @import\n\n; import numpy as np \u2192 aliased_import captures the module name so the\n; import path is resolved and named-binding extraction stores \"np\" \u2192 \"numpy\".\n(import_statement\n name: (aliased_import\n name: (dotted_name) @import.source)) @import\n\n(import_from_statement\n module_name: (dotted_name) @import.source) @import\n\n(import_from_statement\n module_name: (relative_import) @import.source) @import\n\n(call\n function: (identifier) @call.name) @call\n\n(call\n function: (attribute\n attribute: (identifier) @call.name)) @call\n\n; Class attribute type annotations \u2014 PEP 526: address: Address or address: Address = Address()\n; Both bare annotations (address: Address) and annotated assignments (name: str = \"test\")\n; are parsed as (assignment left: ... type: ...) in tree-sitter-python.\n(expression_statement\n (assignment\n left: (identifier) @name\n type: (type)) @definition.property)\n\n; Plain variable assignments without type annotation: x = 5, MAX_SIZE = 100\n; Overlap with @definition.property (typed) is handled by parse-worker dedup.\n(expression_statement\n (assignment\n left: (identifier) @name)) @definition.variable\n\n; Heritage queries - Python class inheritance\n(class_definition\n name: (identifier) @heritage.class\n superclasses: (argument_list\n (identifier) @heritage.extends)) @heritage\n\n; Write access: obj.field = value\n(assignment\n left: (attribute\n object: (_) @assignment.receiver\n attribute: (identifier) @assignment.property)\n right: (_)) @assignment\n\n; Write access: obj.field += value (compound assignment)\n(augmented_assignment\n left: (attribute\n object: (_) @assignment.receiver\n attribute: (identifier) @assignment.property)\n right: (_)) @assignment\n\n; Python HTTP clients: requests.get('/path'), httpx.post('/path'), session.get('/path')\n(call\n function: (attribute\n attribute: (identifier) @http_client.method)\n arguments: (argument_list\n (string (string_content) @http_client.url))) @http_client\n\n; Python decorators: @app.route, @router.get, etc.\n(decorator\n (call\n function: (attribute\n object: (identifier) @decorator.receiver\n attribute: (identifier) @decorator.name)\n arguments: (argument_list\n (string (string_content) @decorator.arg)?))) @decorator\n";
|
|
4
4
|
export declare const JAVA_QUERIES = "\n; Classes, Interfaces, Enums, Annotations\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(enum_declaration name: (identifier) @name) @definition.enum\n(annotation_type_declaration name: (identifier) @name) @definition.annotation\n\n; Methods & Constructors\n(method_declaration name: (identifier) @name) @definition.method\n(constructor_declaration name: (identifier) @name) @definition.constructor\n\n; Fields \u2014 typed field declarations inside class bodies\n(field_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.property\n\n; Imports - capture any import declaration child as source\n(import_declaration (_) @import.source) @import\n\n; Calls\n(method_invocation name: (identifier) @call.name) @call\n(method_invocation object: (_) name: (identifier) @call.name) @call\n(method_reference) @call\n\n; Constructor calls: new Foo()\n(object_creation_expression type: (type_identifier) @call.name) @call\n\n; Local variable declarations inside method bodies\n(local_variable_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.variable\n\n; Heritage - extends class\n(class_declaration name: (identifier) @heritage.class\n (superclass (type_identifier) @heritage.extends)) @heritage\n\n; Heritage - implements interfaces\n(class_declaration name: (identifier) @heritage.class\n (super_interfaces (type_list (type_identifier) @heritage.implements))) @heritage.impl\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_access\n object: (_) @assignment.receiver\n field: (identifier) @assignment.property)\n right: (_)) @assignment\n";
|
|
5
5
|
export declare const C_QUERIES = "\n; Functions (direct declarator)\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n\n; Functions returning pointers (pointer_declarator wraps function_declarator)\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n\n; Functions returning double pointers (nested pointer_declarator)\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name)))) @definition.function\n\n; Structs, Unions, Enums, Typedefs\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(union_specifier name: (type_identifier) @name) @definition.union\n(enum_specifier name: (type_identifier) @name) @definition.enum\n(type_definition declarator: (type_identifier) @name) @definition.typedef\n\n; Macros\n(preproc_function_def name: (identifier) @name) @definition.macro\n(preproc_def name: (identifier) @name) @definition.macro\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n\n; Variable declarations: int x = 5; or int x;\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name)) @definition.variable\n";
|
|
@@ -240,6 +240,12 @@ export const TYPESCRIPT_QUERIES = `
|
|
|
240
240
|
[(string (string_fragment) @route.url)
|
|
241
241
|
(template_string) @route.template_url])) @route.fetch
|
|
242
242
|
|
|
243
|
+
; Custom fetch wrappers: apiFetch('/path'), fetchJSON('/api/data'), httpGet('/users'), etc.
|
|
244
|
+
(call_expression
|
|
245
|
+
function: (identifier) @_wrapper_fn (#match? @_wrapper_fn "^(api(Fetch|Get|Post|Put|Delete|Patch|Request)|fetch(API|JSON|Data|Endpoint|Resource|Url)|http(Fetch|Get|Post|Put|Delete|Patch|Request))$")
|
|
246
|
+
arguments: (arguments
|
|
247
|
+
(string (string_fragment) @route.url))) @route.fetch
|
|
248
|
+
|
|
243
249
|
; axios.get/post/put/delete/patch('/path'), $.get/post/ajax({url:'/path'})
|
|
244
250
|
(call_expression
|
|
245
251
|
function: (member_expression
|
|
@@ -432,6 +438,12 @@ export const JAVASCRIPT_QUERIES = `
|
|
|
432
438
|
[(string (string_fragment) @route.url)
|
|
433
439
|
(template_string) @route.template_url])) @route.fetch
|
|
434
440
|
|
|
441
|
+
; Custom fetch wrappers: apiFetch('/path'), fetchJSON('/api/data'), httpGet('/users'), etc.
|
|
442
|
+
(call_expression
|
|
443
|
+
function: (identifier) @_wrapper_fn (#match? @_wrapper_fn "^(api(Fetch|Get|Post|Put|Delete|Patch|Request)|fetch(API|JSON|Data|Endpoint|Resource|Url)|http(Fetch|Get|Post|Put|Delete|Patch|Request))$")
|
|
444
|
+
arguments: (arguments
|
|
445
|
+
(string (string_fragment) @route.url))) @route.fetch
|
|
446
|
+
|
|
435
447
|
; axios.get/post, $.get/post/ajax
|
|
436
448
|
(call_expression
|
|
437
449
|
function: (member_expression
|
|
@@ -101,6 +101,10 @@ export interface ExtractedFetchCall {
|
|
|
101
101
|
fetchURL: string;
|
|
102
102
|
lineNumber: number;
|
|
103
103
|
}
|
|
104
|
+
export interface FetchWrapperDef {
|
|
105
|
+
filePath: string;
|
|
106
|
+
functionName: string;
|
|
107
|
+
}
|
|
104
108
|
export interface ExtractedDecoratorRoute {
|
|
105
109
|
filePath: string;
|
|
106
110
|
routePath: string;
|
|
@@ -166,6 +170,7 @@ export interface ParseWorkerResult {
|
|
|
166
170
|
heritage: ExtractedHeritage[];
|
|
167
171
|
routes: ExtractedRoute[];
|
|
168
172
|
fetchCalls: ExtractedFetchCall[];
|
|
173
|
+
fetchWrapperDefs: FetchWrapperDef[];
|
|
169
174
|
decoratorRoutes: ExtractedDecoratorRoute[];
|
|
170
175
|
toolDefs: ExtractedToolDef[];
|
|
171
176
|
ormQueries: ExtractedORMQuery[];
|
|
@@ -432,6 +432,7 @@ const processBatch = (files, onProgress) => {
|
|
|
432
432
|
heritage: [],
|
|
433
433
|
routes: [],
|
|
434
434
|
fetchCalls: [],
|
|
435
|
+
fetchWrapperDefs: [],
|
|
435
436
|
decoratorRoutes: [],
|
|
436
437
|
toolDefs: [],
|
|
437
438
|
ormQueries: [],
|
|
@@ -539,6 +540,25 @@ const EXPRESS_ROUTE_METHODS = new Set([
|
|
|
539
540
|
'use',
|
|
540
541
|
'route',
|
|
541
542
|
]);
|
|
543
|
+
/**
|
|
544
|
+
* Walk a tree-sitter AST subtree looking for a call to the global `fetch()` function.
|
|
545
|
+
* Returns `true` if found within `maxDepth` levels of nesting — keeps the check
|
|
546
|
+
* lightweight so it doesn't slow down parse-worker on large function bodies.
|
|
547
|
+
*/
|
|
548
|
+
const checkForFetchCall = (node, depth = 0, maxDepth = 5) => {
|
|
549
|
+
if (depth > maxDepth)
|
|
550
|
+
return false;
|
|
551
|
+
if (node.type === 'call_expression') {
|
|
552
|
+
const fn = node.childForFieldName('function');
|
|
553
|
+
if (fn?.type === 'identifier' && fn.text === 'fetch')
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
557
|
+
if (checkForFetchCall(node.child(i), depth + 1, maxDepth))
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
return false;
|
|
561
|
+
};
|
|
542
562
|
// HTTP client methods that are ONLY used by clients, not Express route registration.
|
|
543
563
|
// Methods like get/post/put/delete/patch overlap with Express — those are captured by
|
|
544
564
|
// the express_route handler as route definitions, not consumers. The fetch() global
|
|
@@ -1505,6 +1525,18 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1505
1525
|
: '',
|
|
1506
1526
|
});
|
|
1507
1527
|
}
|
|
1528
|
+
// ── Fetch wrapper detection: record functions that call fetch() internally ──
|
|
1529
|
+
if (nodeLabel === 'Function' &&
|
|
1530
|
+
definitionNode &&
|
|
1531
|
+
nameNode &&
|
|
1532
|
+
(language === SupportedLanguages.TypeScript || language === SupportedLanguages.JavaScript)) {
|
|
1533
|
+
if (checkForFetchCall(definitionNode)) {
|
|
1534
|
+
result.fetchWrapperDefs.push({
|
|
1535
|
+
filePath: file.path,
|
|
1536
|
+
functionName: nameNode.text,
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1508
1540
|
}
|
|
1509
1541
|
// Extract framework routes via provider detection (e.g., Laravel routes.php)
|
|
1510
1542
|
if (provider.isRouteFile?.(file.path)) {
|
|
@@ -1542,6 +1574,7 @@ let accumulated = {
|
|
|
1542
1574
|
heritage: [],
|
|
1543
1575
|
routes: [],
|
|
1544
1576
|
fetchCalls: [],
|
|
1577
|
+
fetchWrapperDefs: [],
|
|
1545
1578
|
decoratorRoutes: [],
|
|
1546
1579
|
toolDefs: [],
|
|
1547
1580
|
ormQueries: [],
|
|
@@ -1569,6 +1602,7 @@ const mergeResult = (target, src) => {
|
|
|
1569
1602
|
appendAll(target.heritage, src.heritage);
|
|
1570
1603
|
appendAll(target.routes, src.routes);
|
|
1571
1604
|
appendAll(target.fetchCalls, src.fetchCalls);
|
|
1605
|
+
appendAll(target.fetchWrapperDefs, src.fetchWrapperDefs);
|
|
1572
1606
|
appendAll(target.decoratorRoutes, src.decoratorRoutes);
|
|
1573
1607
|
appendAll(target.toolDefs, src.toolDefs);
|
|
1574
1608
|
appendAll(target.ormQueries, src.ormQueries);
|
|
@@ -1651,6 +1685,7 @@ parentPort.on('message', (msg) => {
|
|
|
1651
1685
|
heritage: [],
|
|
1652
1686
|
routes: [],
|
|
1653
1687
|
fetchCalls: [],
|
|
1688
|
+
fetchWrapperDefs: [],
|
|
1654
1689
|
decoratorRoutes: [],
|
|
1655
1690
|
toolDefs: [],
|
|
1656
1691
|
ormQueries: [],
|
|
@@ -41,7 +41,7 @@ import { fileURLToPath } from 'url';
|
|
|
41
41
|
* On version mismatch, `loadParseCache` returns an empty cache and the
|
|
42
42
|
* next save overwrites the on-disk file with the new version baked in.
|
|
43
43
|
*/
|
|
44
|
-
const SCHEMA_BUMP =
|
|
44
|
+
const SCHEMA_BUMP = 2;
|
|
45
45
|
const GITNEXUS_PKG_VERSION = (() => {
|
|
46
46
|
try {
|
|
47
47
|
// package.json sits at gitnexus/package.json — two levels up from
|
package/package.json
CHANGED