@colbymchenry/codegraph-darwin-x64 0.9.6 → 0.9.8
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/lib/dist/bin/codegraph.js +28 -45
- package/lib/dist/bin/codegraph.js.map +1 -1
- package/lib/dist/context/formatter.d.ts.map +1 -1
- package/lib/dist/context/formatter.js +25 -6
- package/lib/dist/context/formatter.js.map +1 -1
- package/lib/dist/context/index.d.ts.map +1 -1
- package/lib/dist/context/index.js +31 -0
- package/lib/dist/context/index.js.map +1 -1
- package/lib/dist/db/queries.d.ts +74 -0
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +182 -0
- package/lib/dist/db/queries.js.map +1 -1
- package/lib/dist/extraction/generated-detection.d.ts +30 -0
- package/lib/dist/extraction/generated-detection.d.ts.map +1 -0
- package/lib/dist/extraction/generated-detection.js +80 -0
- package/lib/dist/extraction/generated-detection.js.map +1 -0
- package/lib/dist/extraction/grammars.d.ts +10 -0
- package/lib/dist/extraction/grammars.d.ts.map +1 -1
- package/lib/dist/extraction/grammars.js +13 -0
- package/lib/dist/extraction/grammars.js.map +1 -1
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +21 -6
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/languages/java.d.ts.map +1 -1
- package/lib/dist/extraction/languages/java.js +6 -0
- package/lib/dist/extraction/languages/java.js.map +1 -1
- package/lib/dist/extraction/languages/kotlin.d.ts.map +1 -1
- package/lib/dist/extraction/languages/kotlin.js +6 -0
- package/lib/dist/extraction/languages/kotlin.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts +10 -0
- package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.d.ts +25 -0
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +125 -1
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
- package/lib/dist/extraction/wasm-runtime-flags.js +1 -0
- package/lib/dist/extraction/wasm-runtime-flags.js.map +1 -1
- package/lib/dist/index.d.ts +33 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +37 -1
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/config-writer.d.ts +7 -8
- package/lib/dist/installer/config-writer.d.ts.map +1 -1
- package/lib/dist/installer/config-writer.js +7 -27
- package/lib/dist/installer/config-writer.js.map +1 -1
- package/lib/dist/installer/index.d.ts +2 -19
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +5 -36
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/installer/instructions-template.d.ts +11 -21
- package/lib/dist/installer/instructions-template.d.ts.map +1 -1
- package/lib/dist/installer/instructions-template.js +12 -56
- package/lib/dist/installer/instructions-template.js.map +1 -1
- package/lib/dist/installer/targets/antigravity.d.ts.map +1 -1
- package/lib/dist/installer/targets/antigravity.js +1 -0
- package/lib/dist/installer/targets/antigravity.js.map +1 -1
- package/lib/dist/installer/targets/claude.d.ts +10 -1
- package/lib/dist/installer/targets/claude.d.ts.map +1 -1
- package/lib/dist/installer/targets/claude.js +25 -40
- package/lib/dist/installer/targets/claude.js.map +1 -1
- package/lib/dist/installer/targets/codex.d.ts.map +1 -1
- package/lib/dist/installer/targets/codex.js +15 -13
- package/lib/dist/installer/targets/codex.js.map +1 -1
- package/lib/dist/installer/targets/cursor.d.ts.map +1 -1
- package/lib/dist/installer/targets/cursor.js +9 -38
- package/lib/dist/installer/targets/cursor.js.map +1 -1
- package/lib/dist/installer/targets/gemini.d.ts.map +1 -1
- package/lib/dist/installer/targets/gemini.js +15 -13
- package/lib/dist/installer/targets/gemini.js.map +1 -1
- package/lib/dist/installer/targets/kiro.d.ts.map +1 -1
- package/lib/dist/installer/targets/kiro.js +9 -27
- package/lib/dist/installer/targets/kiro.js.map +1 -1
- package/lib/dist/installer/targets/opencode.d.ts.map +1 -1
- package/lib/dist/installer/targets/opencode.js +15 -13
- package/lib/dist/installer/targets/opencode.js.map +1 -1
- package/lib/dist/installer/targets/types.d.ts +0 -15
- package/lib/dist/installer/targets/types.d.ts.map +1 -1
- package/lib/dist/mcp/engine.d.ts +6 -1
- package/lib/dist/mcp/engine.d.ts.map +1 -1
- package/lib/dist/mcp/engine.js +21 -42
- package/lib/dist/mcp/engine.js.map +1 -1
- package/lib/dist/mcp/index.d.ts +7 -4
- package/lib/dist/mcp/index.d.ts.map +1 -1
- package/lib/dist/mcp/index.js +46 -39
- package/lib/dist/mcp/index.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts +35 -0
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +223 -0
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/server-instructions.d.ts +1 -1
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +2 -0
- package/lib/dist/mcp/server-instructions.js.map +1 -1
- package/lib/dist/mcp/session.d.ts +10 -0
- package/lib/dist/mcp/session.d.ts.map +1 -1
- package/lib/dist/mcp/session.js +7 -5
- package/lib/dist/mcp/session.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts +39 -1
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +968 -96
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts +2 -2
- package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.js +395 -29
- package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
- package/lib/dist/resolution/import-resolver.d.ts +10 -0
- package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/import-resolver.js +34 -0
- package/lib/dist/resolution/import-resolver.js.map +1 -1
- package/lib/dist/resolution/index.d.ts.map +1 -1
- package/lib/dist/resolution/index.js +15 -0
- package/lib/dist/resolution/index.js.map +1 -1
- package/lib/dist/sync/git-hooks.d.ts.map +1 -1
- package/lib/dist/sync/git-hooks.js +2 -0
- package/lib/dist/sync/git-hooks.js.map +1 -1
- package/lib/dist/sync/worktree.d.ts.map +1 -1
- package/lib/dist/sync/worktree.js +1 -0
- package/lib/dist/sync/worktree.js.map +1 -1
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +1 -1
- package/package.json +1 -1
- package/lib/dist/installer/claude-md-template.d.ts +0 -14
- package/lib/dist/installer/claude-md-template.d.ts.map +0 -1
- package/lib/dist/installer/claude-md-template.js +0 -21
- package/lib/dist/installer/claude-md-template.js.map +0 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.synthesizeCallbackEdges = synthesizeCallbackEdges;
|
|
4
|
+
const generated_detection_1 = require("../extraction/generated-detection");
|
|
5
|
+
const strip_comments_1 = require("./strip-comments");
|
|
4
6
|
const REGISTRAR_NAME = /^(on[A-Z]\w*|subscribe|addListener|addEventListener|register|watch|listen|addCallback)$/;
|
|
5
7
|
const DISPATCHER_NAME = /(emit|trigger|notify|dispatch|fire|publish|flush)/i;
|
|
6
8
|
const MAX_CALLBACKS_PER_CHANNEL = 40;
|
|
@@ -19,6 +21,18 @@ const VUE_HANDLER_RE = /(?:@|v-on:)([a-zA-Z][\w-]*)(?:\.[\w]+)*\s*=\s*"([^"]+)"/
|
|
|
19
21
|
// Composable/hook destructure: `const { close: closeSidebar } = useSidebarControl()`.
|
|
20
22
|
// Captures the destructure body + the called composable; only `use*` calls qualify.
|
|
21
23
|
const VUE_DESTRUCTURE_RE = /(?:const|let|var)\s*\{([^}]+)\}\s*=\s*(\w+)\s*\(/g;
|
|
24
|
+
// Closure-collection dynamic dispatch (language-agnostic, Swift-first). A method
|
|
25
|
+
// appends a closure to a collection property; another method iterates that
|
|
26
|
+
// property *invoking each element* (`coll.forEach { $0() }` / `{ it() }`). The
|
|
27
|
+
// element-invoke (`$0(` / `it(`) PROVES the collection holds closures, so pairing
|
|
28
|
+
// a dispatcher to same-named registrars (`.append`/`.add`/`.push`/`.insert`,
|
|
29
|
+
// incl. Swift `prop.write { $0.append }`) is high-precision. Cross-file/class by
|
|
30
|
+
// design: Alamofire appends in `DataRequest.validate` but iterates in the base
|
|
31
|
+
// `Request.didCompleteTask` — neither same-file nor same-class pairing reaches it.
|
|
32
|
+
const CC_DISPATCH_RE = /(\w+)\.forEach\s*\{\s*(?:\$0|it)\s*\(/g;
|
|
33
|
+
const CC_APPEND_WRITE_RE = /(\w+)\.write\s*\{\s*\$0(?:\.(\w+))?\.(?:append|add|push|insert)\s*\(/g;
|
|
34
|
+
const CC_APPEND_DIRECT_RE = /(\w+)\.(?:append|add|push|insert)\s*\(/g;
|
|
35
|
+
const CC_FANOUT_CAP = 8; // skip a field name with more dispatchers/registrars than this (too generic to pair confidently)
|
|
22
36
|
function kebabToPascal(s) {
|
|
23
37
|
return s.split('-').map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
|
|
24
38
|
}
|
|
@@ -127,6 +141,88 @@ function fieldChannelEdges(queries, ctx) {
|
|
|
127
141
|
}
|
|
128
142
|
return edges;
|
|
129
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Closure-collection dispatch: dispatcher iterates a closure-collection property
|
|
146
|
+
* invoking each element; registrar appends a closure to the same-named property.
|
|
147
|
+
* Emits dispatcher → registrar so a flow reaches the registration site (where the
|
|
148
|
+
* appended closure's body — and its callers — live). High-precision: the
|
|
149
|
+
* dispatcher's element-invoke is the gate (a `.forEach` that does NOT invoke its
|
|
150
|
+
* element is ignored), so a repo with no closure-collection dispatch yields zero
|
|
151
|
+
* edges regardless of how many `.append`/`.push` sites it has.
|
|
152
|
+
*
|
|
153
|
+
* Pairs globally by field name (cross-file/class is required — see Alamofire's
|
|
154
|
+
* base-class `Request.didCompleteTask` iterating `validators` appended by the
|
|
155
|
+
* subclass `DataRequest.validate`), bounded by a fan-out cap so a generic field
|
|
156
|
+
* name shared across unrelated classes can't fan out into noise.
|
|
157
|
+
*/
|
|
158
|
+
function closureCollectionEdges(queries, ctx) {
|
|
159
|
+
const candidates = [...queries.getNodesByKind('method'), ...queries.getNodesByKind('function')];
|
|
160
|
+
const dispatchers = new Map(); // field → dispatcher methods + forEach line
|
|
161
|
+
const registrars = new Map(); // field → registrar methods + append line
|
|
162
|
+
const addReg = (field, node, absLine) => {
|
|
163
|
+
if (!field || /^\d+$/.test(field))
|
|
164
|
+
return; // `$0.append` mis-captures the `0`; the write-RE owns that field
|
|
165
|
+
const arr = registrars.get(field) ?? [];
|
|
166
|
+
if (!arr.some((r) => r.node.id === node.id))
|
|
167
|
+
arr.push({ node, line: absLine });
|
|
168
|
+
registrars.set(field, arr);
|
|
169
|
+
};
|
|
170
|
+
for (const m of candidates) {
|
|
171
|
+
const content = ctx.readFile(m.filePath);
|
|
172
|
+
const src = content && sliceLines(content, m.startLine, m.endLine);
|
|
173
|
+
if (!src)
|
|
174
|
+
continue;
|
|
175
|
+
const hasForEach = src.includes('.forEach');
|
|
176
|
+
const hasAppend = src.includes('.append(') || src.includes('.add(') || src.includes('.push(') || src.includes('.insert(');
|
|
177
|
+
if (!hasForEach && !hasAppend)
|
|
178
|
+
continue;
|
|
179
|
+
const lineAt = (idx) => (m.startLine ?? 1) + src.slice(0, idx).split('\n').length - 1;
|
|
180
|
+
if (hasForEach) {
|
|
181
|
+
CC_DISPATCH_RE.lastIndex = 0;
|
|
182
|
+
let d;
|
|
183
|
+
while ((d = CC_DISPATCH_RE.exec(src))) {
|
|
184
|
+
const arr = dispatchers.get(d[1]) ?? [];
|
|
185
|
+
if (!arr.some((n) => n.node.id === m.id))
|
|
186
|
+
arr.push({ node: m, line: lineAt(d.index) });
|
|
187
|
+
dispatchers.set(d[1], arr);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (hasAppend) {
|
|
191
|
+
CC_APPEND_WRITE_RE.lastIndex = 0;
|
|
192
|
+
let w;
|
|
193
|
+
while ((w = CC_APPEND_WRITE_RE.exec(src)))
|
|
194
|
+
addReg(w[2] || w[1], m, lineAt(w.index)); // nested `$0.streams` else the `.write` receiver
|
|
195
|
+
CC_APPEND_DIRECT_RE.lastIndex = 0;
|
|
196
|
+
let a;
|
|
197
|
+
while ((a = CC_APPEND_DIRECT_RE.exec(src)))
|
|
198
|
+
addReg(a[1], m, lineAt(a.index));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const edges = [];
|
|
202
|
+
const seen = new Set();
|
|
203
|
+
for (const [field, disps] of dispatchers) {
|
|
204
|
+
const regs = registrars.get(field);
|
|
205
|
+
if (!regs || regs.length === 0)
|
|
206
|
+
continue;
|
|
207
|
+
if (disps.length > CC_FANOUT_CAP || regs.length > CC_FANOUT_CAP)
|
|
208
|
+
continue; // generic field — can't pair confidently
|
|
209
|
+
for (const disp of disps)
|
|
210
|
+
for (const reg of regs) {
|
|
211
|
+
if (disp.node.id === reg.node.id)
|
|
212
|
+
continue;
|
|
213
|
+
const key = `${disp.node.id}>${reg.node.id}`;
|
|
214
|
+
if (seen.has(key))
|
|
215
|
+
continue;
|
|
216
|
+
seen.add(key);
|
|
217
|
+
edges.push({
|
|
218
|
+
source: disp.node.id, target: reg.node.id, kind: 'calls', line: disp.line,
|
|
219
|
+
provenance: 'heuristic',
|
|
220
|
+
metadata: { synthesizedBy: 'closure-collection', field, registeredAt: `${reg.node.filePath}:${reg.line}` },
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return edges;
|
|
225
|
+
}
|
|
130
226
|
/** Phase 2: string-keyed EventEmitter channels (on('e', fn) ↔ emit('e')). */
|
|
131
227
|
function eventEmitterEdges(ctx) {
|
|
132
228
|
const emitsByEvent = new Map(); // event → dispatcher node ids
|
|
@@ -342,7 +438,16 @@ function cppOverrideEdges(queries) {
|
|
|
342
438
|
* trace/callees reach the implementation. Over-approximation accepted
|
|
343
439
|
* (reachability-correct); capped per class, gated to JVM languages.
|
|
344
440
|
*/
|
|
345
|
-
|
|
441
|
+
// Languages whose static `implements`/`extends` edges should bridge an
|
|
442
|
+
// interface (or abstract base) method to the matching concrete-class method.
|
|
443
|
+
// The set is "languages with explicit nominal subtyping and a single class
|
|
444
|
+
// kind that holds methods" — i.e. the shape this loop expects. Swift and
|
|
445
|
+
// Scala fit shape-wise (Swift `protocol`/`class`, Scala `trait`/`class`)
|
|
446
|
+
// and are added below; their concrete-side nodes can be a `struct` (Swift)
|
|
447
|
+
// or an `object` (Scala) so the loop also iterates those kinds.
|
|
448
|
+
const IFACE_OVERRIDE_LANGS = new Set([
|
|
449
|
+
'java', 'kotlin', 'csharp', 'typescript', 'javascript', 'swift', 'scala',
|
|
450
|
+
]);
|
|
346
451
|
function interfaceOverrideEdges(queries) {
|
|
347
452
|
const edges = [];
|
|
348
453
|
const seen = new Set();
|
|
@@ -350,46 +455,163 @@ function interfaceOverrideEdges(queries) {
|
|
|
350
455
|
.getOutgoingEdges(classId, ['contains'])
|
|
351
456
|
.map((e) => queries.getNodeById(e.target))
|
|
352
457
|
.filter((n) => !!n && n.kind === 'method');
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
458
|
+
// Concrete-side kinds vary by language: `class` covers Java / Kotlin /
|
|
459
|
+
// C# / TS / Swift-classes / Scala-classes; `struct` covers Swift value
|
|
460
|
+
// types that conform to protocols. Iterate both.
|
|
461
|
+
const concreteKinds = ['class', 'struct'];
|
|
462
|
+
for (const kind of concreteKinds) {
|
|
463
|
+
for (const cls of queries.getNodesByKind(kind)) {
|
|
464
|
+
const implMethods = methodsOf(cls.id).filter((n) => IFACE_OVERRIDE_LANGS.has(n.language));
|
|
465
|
+
if (implMethods.length === 0)
|
|
360
466
|
continue;
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
implByName.
|
|
467
|
+
for (const sup of queries.getOutgoingEdges(cls.id, ['implements', 'extends'])) {
|
|
468
|
+
const base = queries.getNodeById(sup.target);
|
|
469
|
+
if (!base || !IFACE_OVERRIDE_LANGS.has(base.language) || base.id === cls.id)
|
|
470
|
+
continue;
|
|
471
|
+
// Group impl methods by name to handle OVERLOADS: an interface `list()` and
|
|
472
|
+
// `list(params)` are distinct nodes and a call may resolve to either, so
|
|
473
|
+
// link every base overload → every same-name impl overload (keying by name
|
|
474
|
+
// alone would drop all but one and miss the resolved overload).
|
|
475
|
+
const implByName = new Map();
|
|
476
|
+
for (const m of implMethods) {
|
|
477
|
+
const arr = implByName.get(m.name);
|
|
478
|
+
if (arr)
|
|
479
|
+
arr.push(m);
|
|
480
|
+
else
|
|
481
|
+
implByName.set(m.name, [m]);
|
|
482
|
+
}
|
|
483
|
+
let added = 0;
|
|
484
|
+
for (const bm of methodsOf(base.id)) {
|
|
485
|
+
if (added >= MAX_CALLBACKS_PER_CHANNEL)
|
|
486
|
+
break;
|
|
487
|
+
for (const m of implByName.get(bm.name) ?? []) {
|
|
488
|
+
if (added >= MAX_CALLBACKS_PER_CHANNEL)
|
|
489
|
+
break;
|
|
490
|
+
if (bm.id === m.id)
|
|
491
|
+
continue;
|
|
492
|
+
const key = `${bm.id}>${m.id}`;
|
|
493
|
+
if (seen.has(key))
|
|
494
|
+
continue;
|
|
495
|
+
seen.add(key);
|
|
496
|
+
edges.push({
|
|
497
|
+
source: bm.id,
|
|
498
|
+
target: m.id,
|
|
499
|
+
kind: 'calls',
|
|
500
|
+
line: bm.startLine,
|
|
501
|
+
provenance: 'heuristic',
|
|
502
|
+
metadata: { synthesizedBy: 'interface-impl', via: m.name, registeredAt: `${m.filePath}:${m.startLine}` },
|
|
503
|
+
});
|
|
504
|
+
added++;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
372
507
|
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return edges;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Go gRPC stub → impl bridge. The protoc-gen-go-grpc codegen emits an
|
|
514
|
+
* `UnimplementedXxxServer` struct in `*_grpc.pb.go` carrying one method
|
|
515
|
+
* per service RPC; the real handler is a hand-written struct in another
|
|
516
|
+
* file (`x/bank/keeper/msg_server.go::msgServer.Send` in cosmos-sdk).
|
|
517
|
+
* Go's structural typing means no `implements` edge exists for our
|
|
518
|
+
* resolver to follow, so `trace("Send","SendCoins")` lands on the
|
|
519
|
+
* empty stub and reports "no path" (validated empirically — the cosmos
|
|
520
|
+
* Q1 r1 trace failure that drove this work).
|
|
521
|
+
*
|
|
522
|
+
* Bridge: for each `UnimplementedXxxServer` whose RPC-method names are
|
|
523
|
+
* a SUBSET of some other Go struct's method names, emit `calls` edges
|
|
524
|
+
* `stub.method → impl.method` (paired by name). Excludes the gRPC
|
|
525
|
+
* internal markers `mustEmbedUnimplementedXxxServer` and
|
|
526
|
+
* `testEmbeddedByValue`, and skips candidate impls that themselves
|
|
527
|
+
* live in a generated file (their `xxxClient` / sibling stubs would
|
|
528
|
+
* otherwise look like impls).
|
|
529
|
+
*
|
|
530
|
+
* Multiple candidates is allowed and capped at MAX_CALLBACKS_PER_CHANNEL —
|
|
531
|
+
* a service often has both a production impl and one or more test
|
|
532
|
+
* mocks; linking to all preserves trace utility without false-favoring.
|
|
533
|
+
*
|
|
534
|
+
* Provenance: `heuristic`, `synthesizedBy: 'go-grpc-stub-impl'`. The
|
|
535
|
+
* stub's source line is the wiring site shown in the trace trail.
|
|
536
|
+
*/
|
|
537
|
+
function goGrpcStubImplEdges(queries) {
|
|
538
|
+
const edges = [];
|
|
539
|
+
const seen = new Set();
|
|
540
|
+
const STUB_RE = /^Unimplemented.*Server$/;
|
|
541
|
+
// gRPC internal-helper methods that appear on every Unimplemented*Server;
|
|
542
|
+
// not part of the service contract, so exclude when computing the RPC-method
|
|
543
|
+
// signature used to match impls.
|
|
544
|
+
const isInternalMarker = (n) => n.startsWith('mustEmbed') || n === 'testEmbeddedByValue';
|
|
545
|
+
// Methods directly contained by each Go struct, name-only. Built once.
|
|
546
|
+
const methodNamesByStruct = new Map();
|
|
547
|
+
const methodNodesByStruct = new Map();
|
|
548
|
+
const goStructs = [];
|
|
549
|
+
for (const s of queries.getNodesByKind('struct')) {
|
|
550
|
+
if (s.language !== 'go')
|
|
551
|
+
continue;
|
|
552
|
+
goStructs.push(s);
|
|
553
|
+
const ms = queries
|
|
554
|
+
.getOutgoingEdges(s.id, ['contains'])
|
|
555
|
+
.map((e) => queries.getNodeById(e.target))
|
|
556
|
+
.filter((n) => !!n && n.kind === 'method');
|
|
557
|
+
methodNodesByStruct.set(s.id, ms);
|
|
558
|
+
methodNamesByStruct.set(s.id, new Set(ms.map((m) => m.name)));
|
|
559
|
+
}
|
|
560
|
+
for (const stub of goStructs) {
|
|
561
|
+
if (!STUB_RE.test(stub.name))
|
|
562
|
+
continue;
|
|
563
|
+
// The stub MUST live in a generated file — that's what tells us this is
|
|
564
|
+
// a protoc-emitted scaffold rather than someone naming a struct
|
|
565
|
+
// `UnimplementedXxxServer` by hand. Without this gate we'd also bridge
|
|
566
|
+
// such hand-written structs and create misleading edges.
|
|
567
|
+
if (!(0, generated_detection_1.isGeneratedFile)(stub.filePath))
|
|
568
|
+
continue;
|
|
569
|
+
const stubMethods = (methodNodesByStruct.get(stub.id) ?? []).filter((m) => !isInternalMarker(m.name));
|
|
570
|
+
if (stubMethods.length === 0)
|
|
571
|
+
continue;
|
|
572
|
+
const stubMethodNames = stubMethods.map((m) => m.name);
|
|
573
|
+
for (const cand of goStructs) {
|
|
574
|
+
if (cand.id === stub.id)
|
|
575
|
+
continue;
|
|
576
|
+
// Skip generated-file candidates — they're siblings (msgClient,
|
|
577
|
+
// UnsafeMsgServer, …) whose method sets coincidentally match.
|
|
578
|
+
if ((0, generated_detection_1.isGeneratedFile)(cand.filePath))
|
|
579
|
+
continue;
|
|
580
|
+
const candNames = methodNamesByStruct.get(cand.id);
|
|
581
|
+
if (!candNames)
|
|
582
|
+
continue;
|
|
583
|
+
// Subset: every RPC method must exist on the candidate by name.
|
|
584
|
+
// Signature-level match would tighten this further, but name-match
|
|
585
|
+
// alone already gives one-to-one pairing in real codebases because
|
|
586
|
+
// gRPC method-name sets are highly distinctive (Send + MultiSend +
|
|
587
|
+
// UpdateParams + SetSendEnabled is unique to bank's MsgServer).
|
|
588
|
+
if (!stubMethodNames.every((n) => candNames.has(n)))
|
|
589
|
+
continue;
|
|
590
|
+
const candMethods = methodNodesByStruct.get(cand.id) ?? [];
|
|
373
591
|
let added = 0;
|
|
374
|
-
for (const
|
|
592
|
+
for (const sm of stubMethods) {
|
|
375
593
|
if (added >= MAX_CALLBACKS_PER_CHANNEL)
|
|
376
594
|
break;
|
|
377
|
-
for (const
|
|
595
|
+
for (const cm of candMethods) {
|
|
378
596
|
if (added >= MAX_CALLBACKS_PER_CHANNEL)
|
|
379
597
|
break;
|
|
380
|
-
if (
|
|
598
|
+
if (cm.name !== sm.name)
|
|
381
599
|
continue;
|
|
382
|
-
const key = `${
|
|
600
|
+
const key = `${sm.id}>${cm.id}`;
|
|
383
601
|
if (seen.has(key))
|
|
384
602
|
continue;
|
|
385
603
|
seen.add(key);
|
|
386
604
|
edges.push({
|
|
387
|
-
source:
|
|
388
|
-
target:
|
|
605
|
+
source: sm.id,
|
|
606
|
+
target: cm.id,
|
|
389
607
|
kind: 'calls',
|
|
390
|
-
line:
|
|
608
|
+
line: sm.startLine,
|
|
391
609
|
provenance: 'heuristic',
|
|
392
|
-
metadata: {
|
|
610
|
+
metadata: {
|
|
611
|
+
synthesizedBy: 'go-grpc-stub-impl',
|
|
612
|
+
via: cm.name,
|
|
613
|
+
registeredAt: `${cm.filePath}:${cm.startLine}`,
|
|
614
|
+
},
|
|
393
615
|
});
|
|
394
616
|
added++;
|
|
395
617
|
}
|
|
@@ -878,14 +1100,153 @@ function mybatisJavaXmlEdges(queries) {
|
|
|
878
1100
|
}
|
|
879
1101
|
return edges;
|
|
880
1102
|
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Gin middleware chain. Gin runs its entire handler chain through one dynamic
|
|
1105
|
+
* line in `(*Context).Next`:
|
|
1106
|
+
* for c.index < len(c.handlers) { c.handlers[c.index](c); c.index++ }
|
|
1107
|
+
* `c.handlers` is a `HandlersChain` (`[]HandlerFunc`) assembled at registration
|
|
1108
|
+
* time by `combineHandlers` from the funcs passed to `r.Use(...)` /
|
|
1109
|
+
* `r.GET("/path", h...)` / `r.Handle(...)`. Because the call is a computed index
|
|
1110
|
+
* into a runtime-built slice, tree-sitter resolves `c.handlers[c.index](c)` to
|
|
1111
|
+
* NOTHING — so `callees(Next)` is just the `len()` helper and the flow
|
|
1112
|
+
* `ServeHTTP → handleHTTPRequest → Next` dead-ends at the exact symbol the
|
|
1113
|
+
* "how do requests flow through the middleware chain" question is about. The
|
|
1114
|
+
* agent then re-queries Next and falls back to Read/grep (validated: the gin
|
|
1115
|
+
* WITH-arm rabbit-holed on precisely this dead-end).
|
|
1116
|
+
*
|
|
1117
|
+
* Bridge it: find the chain DISPATCHER (a Go method whose body invokes a
|
|
1118
|
+
* `handlers` slice by index) and link it → every HandlerFunc registered via a
|
|
1119
|
+
* gin registration call, so `callees(Next)` and `trace(ServeHTTP, <handler>)`
|
|
1120
|
+
* connect end-to-end. Named handlers only (`gin.Logger()` → `Logger`,
|
|
1121
|
+
* `authMiddleware`); inline closures are anonymous and skipped. Like
|
|
1122
|
+
* react-render / interface-impl this is a deliberate over-approximation —
|
|
1123
|
+
* reachability-correct (any registered handler CAN run for some route), capped,
|
|
1124
|
+
* and gated on the dispatcher existing so it never runs on non-gin Go repos.
|
|
1125
|
+
* Provenance `heuristic`, `synthesizedBy:'gin-middleware-chain'`; `registeredAt`
|
|
1126
|
+
* is the `.Use`/`.GET` site an agent would otherwise grep for.
|
|
1127
|
+
*/
|
|
1128
|
+
const GIN_DISPATCH_RE = /\.handlers\s*\[[^\]]*\]\s*\(/; // c.handlers[c.index](c)
|
|
1129
|
+
const GIN_REG_RE = /\.(?:Use|GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|Any|Handle)\s*\(/g;
|
|
1130
|
+
/** Balanced `(...)` body starting at the '(' index; null if unbalanced. */
|
|
1131
|
+
function goBalancedArgs(s, openIdx) {
|
|
1132
|
+
let depth = 0;
|
|
1133
|
+
for (let i = openIdx; i < s.length; i++) {
|
|
1134
|
+
const c = s[i];
|
|
1135
|
+
if (c === '(')
|
|
1136
|
+
depth++;
|
|
1137
|
+
else if (c === ')') {
|
|
1138
|
+
depth--;
|
|
1139
|
+
if (depth === 0)
|
|
1140
|
+
return s.slice(openIdx + 1, i);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
/** Split a top-level comma list, respecting nested () [] {}. */
|
|
1146
|
+
function goSplitArgs(args) {
|
|
1147
|
+
const out = [];
|
|
1148
|
+
let depth = 0, cur = '';
|
|
1149
|
+
for (const c of args) {
|
|
1150
|
+
if (c === '(' || c === '[' || c === '{') {
|
|
1151
|
+
depth++;
|
|
1152
|
+
cur += c;
|
|
1153
|
+
}
|
|
1154
|
+
else if (c === ')' || c === ']' || c === '}') {
|
|
1155
|
+
depth--;
|
|
1156
|
+
cur += c;
|
|
1157
|
+
}
|
|
1158
|
+
else if (c === ',' && depth === 0) {
|
|
1159
|
+
out.push(cur);
|
|
1160
|
+
cur = '';
|
|
1161
|
+
}
|
|
1162
|
+
else
|
|
1163
|
+
cur += c;
|
|
1164
|
+
}
|
|
1165
|
+
if (cur.trim())
|
|
1166
|
+
out.push(cur);
|
|
1167
|
+
return out;
|
|
1168
|
+
}
|
|
1169
|
+
/** Tail ident of a handler arg: `gin.Logger()`→`Logger`, `mw`→`mw`; null for string paths / closures. */
|
|
1170
|
+
function goHandlerIdent(expr) {
|
|
1171
|
+
const cleaned = expr.trim().replace(/\(\s*\)$/, ''); // drop a trailing call ()
|
|
1172
|
+
if (!cleaned || cleaned.startsWith('"') || cleaned.startsWith('`') || cleaned.startsWith('func'))
|
|
1173
|
+
return null;
|
|
1174
|
+
const m = cleaned.match(/(?:\.|^)([A-Za-z_]\w*)$/);
|
|
1175
|
+
return m ? m[1] : null;
|
|
1176
|
+
}
|
|
1177
|
+
function ginMiddlewareChainEdges(queries, ctx) {
|
|
1178
|
+
// 1. Find the chain dispatcher(s): a Go method that invokes a `handlers` slice by index.
|
|
1179
|
+
const dispatchers = queries.getNodesByKind('method').filter((n) => {
|
|
1180
|
+
if (n.language !== 'go')
|
|
1181
|
+
return false;
|
|
1182
|
+
const content = ctx.readFile(n.filePath);
|
|
1183
|
+
const src = content && sliceLines(content, n.startLine, n.endLine);
|
|
1184
|
+
return !!src && GIN_DISPATCH_RE.test(src);
|
|
1185
|
+
});
|
|
1186
|
+
if (dispatchers.length === 0)
|
|
1187
|
+
return []; // not a gin repo — bail
|
|
1188
|
+
// 2. Collect handler identifiers registered via gin registration calls
|
|
1189
|
+
// (.Use / .GET / … / .Handle). String args (paths/methods) and inline
|
|
1190
|
+
// closures are dropped by goHandlerIdent; the rest are HandlerFuncs.
|
|
1191
|
+
const registered = new Map(); // name → registeredAt (file:line)
|
|
1192
|
+
for (const file of ctx.getAllFiles()) {
|
|
1193
|
+
if (!file.endsWith('.go'))
|
|
1194
|
+
continue;
|
|
1195
|
+
const content = ctx.readFile(file);
|
|
1196
|
+
if (!content || (!content.includes('.Use(') && !/\.(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|Any|Handle)\(/.test(content)))
|
|
1197
|
+
continue;
|
|
1198
|
+
const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'go');
|
|
1199
|
+
GIN_REG_RE.lastIndex = 0;
|
|
1200
|
+
let m;
|
|
1201
|
+
while ((m = GIN_REG_RE.exec(safe))) {
|
|
1202
|
+
const parenIdx = m.index + m[0].length - 1;
|
|
1203
|
+
const argStr = goBalancedArgs(safe, parenIdx);
|
|
1204
|
+
if (!argStr)
|
|
1205
|
+
continue;
|
|
1206
|
+
const line = safe.slice(0, m.index).split('\n').length;
|
|
1207
|
+
for (const arg of goSplitArgs(argStr)) {
|
|
1208
|
+
const name = goHandlerIdent(arg);
|
|
1209
|
+
if (name && !registered.has(name))
|
|
1210
|
+
registered.set(name, `${file}:${line}`);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (registered.size === 0)
|
|
1215
|
+
return [];
|
|
1216
|
+
// 3. Link each dispatcher → each registered handler node (dedup, capped).
|
|
1217
|
+
const edges = [];
|
|
1218
|
+
const seen = new Set();
|
|
1219
|
+
for (const disp of dispatchers) {
|
|
1220
|
+
let added = 0;
|
|
1221
|
+
for (const [name, registeredAt] of registered) {
|
|
1222
|
+
if (added >= MAX_CALLBACKS_PER_CHANNEL)
|
|
1223
|
+
break;
|
|
1224
|
+
const handler = ctx.getNodesByName(name).find((n) => (n.kind === 'function' || n.kind === 'method') && n.language === 'go');
|
|
1225
|
+
if (!handler || handler.id === disp.id)
|
|
1226
|
+
continue;
|
|
1227
|
+
const key = `${disp.id}>${handler.id}`;
|
|
1228
|
+
if (seen.has(key))
|
|
1229
|
+
continue;
|
|
1230
|
+
seen.add(key);
|
|
1231
|
+
edges.push({
|
|
1232
|
+
source: disp.id, target: handler.id, kind: 'calls', line: disp.startLine,
|
|
1233
|
+
provenance: 'heuristic',
|
|
1234
|
+
metadata: { synthesizedBy: 'gin-middleware-chain', via: name, registeredAt },
|
|
1235
|
+
});
|
|
1236
|
+
added++;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return edges;
|
|
1240
|
+
}
|
|
881
1241
|
/**
|
|
882
1242
|
* Synthesize dispatcher→callback edges (field observers + EventEmitters +
|
|
883
1243
|
* React re-render + JSX children + Vue templates + RN event channel +
|
|
884
|
-
* Fabric native-impl + MyBatis Java↔XML). Returns the
|
|
885
|
-
* throws into indexing — callers wrap in try/catch.
|
|
1244
|
+
* Fabric native-impl + MyBatis Java↔XML + Gin middleware chain). Returns the
|
|
1245
|
+
* count added. Never throws into indexing — callers wrap in try/catch.
|
|
886
1246
|
*/
|
|
887
1247
|
function synthesizeCallbackEdges(queries, ctx) {
|
|
888
1248
|
const fieldEdges = fieldChannelEdges(queries, ctx);
|
|
1249
|
+
const closureCollEdges = closureCollectionEdges(queries, ctx);
|
|
889
1250
|
const emitterEdges = eventEmitterEdges(ctx);
|
|
890
1251
|
const renderEdges = reactRenderEdges(queries, ctx);
|
|
891
1252
|
const jsxEdges = reactJsxChildEdges(ctx);
|
|
@@ -893,13 +1254,16 @@ function synthesizeCallbackEdges(queries, ctx) {
|
|
|
893
1254
|
const flutterEdges = flutterBuildEdges(queries, ctx);
|
|
894
1255
|
const cppEdges = cppOverrideEdges(queries);
|
|
895
1256
|
const ifaceEdges = interfaceOverrideEdges(queries);
|
|
1257
|
+
const goGrpcEdges = goGrpcStubImplEdges(queries);
|
|
896
1258
|
const rnEventEdgesList = rnEventEdges(ctx);
|
|
897
1259
|
const fabricNativeEdges = fabricNativeImplEdges(ctx);
|
|
898
1260
|
const mybatisEdges = mybatisJavaXmlEdges(queries);
|
|
1261
|
+
const ginEdges = ginMiddlewareChainEdges(queries, ctx);
|
|
899
1262
|
const merged = [];
|
|
900
1263
|
const seen = new Set();
|
|
901
1264
|
for (const e of [
|
|
902
1265
|
...fieldEdges,
|
|
1266
|
+
...closureCollEdges,
|
|
903
1267
|
...emitterEdges,
|
|
904
1268
|
...renderEdges,
|
|
905
1269
|
...jsxEdges,
|
|
@@ -907,9 +1271,11 @@ function synthesizeCallbackEdges(queries, ctx) {
|
|
|
907
1271
|
...flutterEdges,
|
|
908
1272
|
...cppEdges,
|
|
909
1273
|
...ifaceEdges,
|
|
1274
|
+
...goGrpcEdges,
|
|
910
1275
|
...rnEventEdgesList,
|
|
911
1276
|
...fabricNativeEdges,
|
|
912
1277
|
...mybatisEdges,
|
|
1278
|
+
...ginEdges,
|
|
913
1279
|
]) {
|
|
914
1280
|
const key = `${e.source}>${e.target}`;
|
|
915
1281
|
if (seen.has(key))
|