@colbymchenry/codegraph-darwin-arm64 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/dist/db/index.d.ts +17 -5
- package/lib/dist/db/index.d.ts.map +1 -1
- package/lib/dist/db/index.js +68 -12
- package/lib/dist/db/index.js.map +1 -1
- package/lib/dist/db/queries.d.ts +25 -1
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +39 -0
- package/lib/dist/db/queries.js.map +1 -1
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +84 -31
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +21 -3
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/mcp/daemon.d.ts.map +1 -1
- package/lib/dist/mcp/daemon.js +23 -1
- package/lib/dist/mcp/daemon.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +8 -0
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/query-pool.d.ts +14 -0
- package/lib/dist/mcp/query-pool.d.ts.map +1 -1
- package/lib/dist/mcp/query-pool.js +18 -0
- package/lib/dist/mcp/query-pool.js.map +1 -1
- package/lib/dist/mcp/session.d.ts.map +1 -1
- package/lib/dist/mcp/session.js +6 -0
- package/lib/dist/mcp/session.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +14 -9
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/mcp/transport.d.ts.map +1 -1
- package/lib/dist/mcp/transport.js +18 -0
- package/lib/dist/mcp/transport.js.map +1 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/c-fnptr-synthesizer.js +170 -146
- package/lib/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.js +322 -93
- package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
- package/lib/dist/resolution/goframe-synthesizer.d.ts +2 -1
- package/lib/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/goframe-synthesizer.js +8 -3
- package/lib/dist/resolution/goframe-synthesizer.js.map +1 -1
- package/lib/dist/resolution/index.d.ts +10 -0
- package/lib/dist/resolution/index.d.ts.map +1 -1
- package/lib/dist/resolution/index.js +55 -15
- package/lib/dist/resolution/index.js.map +1 -1
- package/lib/dist/resolution/types.d.ts +9 -0
- package/lib/dist/resolution/types.d.ts.map +1 -1
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +1 -1
- package/package.json +1 -1
|
@@ -276,10 +276,13 @@ async function closureCollectionEdges(queries, ctx, onYield) {
|
|
|
276
276
|
}
|
|
277
277
|
/** Phase 2: string-keyed EventEmitter channels (on('e', fn) ↔ emit('e')). */
|
|
278
278
|
async function eventEmitterEdges(ctx, onYield) {
|
|
279
|
+
let scannedFiles = 0;
|
|
279
280
|
const emitsByEvent = new Map(); // event → dispatcher node ids
|
|
280
281
|
const handlersByEvent = new Map(); // event → handler id → registration site (file:line)
|
|
281
282
|
let scanned = 0;
|
|
282
283
|
for (const file of ctx.getAllFiles()) {
|
|
284
|
+
if ((++scannedFiles & 15) === 0)
|
|
285
|
+
await onYield();
|
|
283
286
|
if ((++scanned & 255) === 0)
|
|
284
287
|
await onYield(); // #1091: yield mid-scan on huge graphs
|
|
285
288
|
const content = ctx.readFile(file);
|
|
@@ -353,10 +356,13 @@ async function eventEmitterEdges(ctx, onYield) {
|
|
|
353
356
|
* `this.setState`). Over-approximation (all setState methods reach render) is
|
|
354
357
|
* accepted — it's reachability-correct, like the callback channels.
|
|
355
358
|
*/
|
|
356
|
-
function reactRenderEdges(queries, ctx) {
|
|
359
|
+
async function reactRenderEdges(queries, ctx, onYield) {
|
|
360
|
+
let scanned255 = 0;
|
|
357
361
|
const edges = [];
|
|
358
362
|
const seen = new Set();
|
|
359
|
-
for (const cls of queries.
|
|
363
|
+
for (const cls of queries.iterateNodesByKind('class')) {
|
|
364
|
+
if ((++scanned255 & 63) === 0)
|
|
365
|
+
await onYield();
|
|
360
366
|
const children = queries.getOutgoingEdges(cls.id, ['contains'])
|
|
361
367
|
.map((e) => queries.getNodeById(e.target))
|
|
362
368
|
.filter((n) => !!n && n.kind === 'method');
|
|
@@ -396,10 +402,13 @@ function reactRenderEdges(queries, ctx) {
|
|
|
396
402
|
* body calls `setState(` → `build`. The setState gate + `.dart` file keep this to
|
|
397
403
|
* Flutter State classes. Over-approximation accepted (reachability-correct).
|
|
398
404
|
*/
|
|
399
|
-
function flutterBuildEdges(queries, ctx) {
|
|
405
|
+
async function flutterBuildEdges(queries, ctx, onYield) {
|
|
406
|
+
let scanned255 = 0;
|
|
400
407
|
const edges = [];
|
|
401
408
|
const seen = new Set();
|
|
402
|
-
for (const cls of queries.
|
|
409
|
+
for (const cls of queries.iterateNodesByKind('class')) {
|
|
410
|
+
if ((++scanned255 & 63) === 0)
|
|
411
|
+
await onYield();
|
|
403
412
|
const children = queries.getOutgoingEdges(cls.id, ['contains'])
|
|
404
413
|
.map((e) => queries.getNodeById(e.target))
|
|
405
414
|
.filter((n) => !!n && n.kind === 'method');
|
|
@@ -458,10 +467,13 @@ const ARKUI_ARRAY_MUTATORS = 'push|pop|shift|unshift|splice|sort|reverse|fill';
|
|
|
458
467
|
* with no reactive properties, gets nothing (this is the precision line the
|
|
459
468
|
* all-sibling-methods design would erase).
|
|
460
469
|
*/
|
|
461
|
-
function arkuiStateBuildEdges(queries, ctx) {
|
|
470
|
+
async function arkuiStateBuildEdges(queries, ctx, onYield) {
|
|
471
|
+
let scanned255 = 0;
|
|
462
472
|
const edges = [];
|
|
463
473
|
const seen = new Set();
|
|
464
|
-
for (const struct of queries.
|
|
474
|
+
for (const struct of queries.iterateNodesByKind('struct')) {
|
|
475
|
+
if ((++scanned255 & 63) === 0)
|
|
476
|
+
await onYield();
|
|
465
477
|
if (struct.language !== 'arkts')
|
|
466
478
|
continue;
|
|
467
479
|
const children = queries.getOutgoingEdges(struct.id, ['contains'])
|
|
@@ -526,7 +538,8 @@ const ARKUI_EMITTER_FANOUT_CAP = 8;
|
|
|
526
538
|
* handling — their bodies' calls already attribute to the registering method,
|
|
527
539
|
* so targeting that method keeps the chain connected.
|
|
528
540
|
*/
|
|
529
|
-
function arkuiEmitterEdges(ctx) {
|
|
541
|
+
async function arkuiEmitterEdges(ctx, onYield) {
|
|
542
|
+
let scannedFiles = 0;
|
|
530
543
|
// bucket key -> emit sites / handler sites
|
|
531
544
|
const emits = new Map();
|
|
532
545
|
const handlers = new Map();
|
|
@@ -542,6 +555,8 @@ function arkuiEmitterEdges(ctx) {
|
|
|
542
555
|
return '';
|
|
543
556
|
};
|
|
544
557
|
for (const file of ctx.getAllFiles()) {
|
|
558
|
+
if ((++scannedFiles & 15) === 0)
|
|
559
|
+
await onYield();
|
|
545
560
|
if (!file.endsWith('.ets'))
|
|
546
561
|
continue;
|
|
547
562
|
const content = ctx.readFile(file);
|
|
@@ -634,7 +649,7 @@ const ARKUI_ROUTER_RE = /\brouter\s*\.\s*(?:pushUrl|replaceUrl)\s*\(\s*\{[^)]{0,
|
|
|
634
649
|
* anything still ambiguous is dropped rather than guessed. Only `@Entry`
|
|
635
650
|
* structs qualify as targets — the decorator is what makes a file a page.
|
|
636
651
|
*/
|
|
637
|
-
function arkuiRouterEdges(ctx) {
|
|
652
|
+
async function arkuiRouterEdges(ctx, onYield) {
|
|
638
653
|
const edges = [];
|
|
639
654
|
const seen = new Set();
|
|
640
655
|
const allFiles = ctx.getAllFiles();
|
|
@@ -649,7 +664,10 @@ function arkuiRouterEdges(ctx) {
|
|
|
649
664
|
}
|
|
650
665
|
return '';
|
|
651
666
|
};
|
|
667
|
+
let scannedFiles = 0;
|
|
652
668
|
for (const file of allFiles) {
|
|
669
|
+
if ((++scannedFiles & 15) === 0)
|
|
670
|
+
await onYield();
|
|
653
671
|
if (!file.endsWith('.ets'))
|
|
654
672
|
continue;
|
|
655
673
|
const content = ctx.readFile(file);
|
|
@@ -704,14 +722,17 @@ function arkuiRouterEdges(ctx) {
|
|
|
704
722
|
* implementation(s). Over-approximation accepted (reachability-correct); capped
|
|
705
723
|
* per class and gated to C++ to avoid touching other languages' dispatch.
|
|
706
724
|
*/
|
|
707
|
-
function cppOverrideEdges(queries) {
|
|
725
|
+
async function cppOverrideEdges(queries, onYield) {
|
|
726
|
+
let scanned255 = 0;
|
|
708
727
|
const edges = [];
|
|
709
728
|
const seen = new Set();
|
|
710
729
|
const methodsOf = (classId) => queries
|
|
711
730
|
.getOutgoingEdges(classId, ['contains'])
|
|
712
731
|
.map((e) => queries.getNodeById(e.target))
|
|
713
732
|
.filter((n) => !!n && n.kind === 'method');
|
|
714
|
-
for (const cls of queries.
|
|
733
|
+
for (const cls of queries.iterateNodesByKind('class')) {
|
|
734
|
+
if ((++scanned255 & 63) === 0)
|
|
735
|
+
await onYield();
|
|
715
736
|
const subMethods = methodsOf(cls.id).filter((n) => n.language === 'cpp');
|
|
716
737
|
if (subMethods.length === 0)
|
|
717
738
|
continue;
|
|
@@ -779,7 +800,8 @@ const IFACE_OVERRIDE_LANGS = new Set([
|
|
|
779
800
|
* with the other dispatch synthesizers; capped per interface. Empty interfaces
|
|
780
801
|
* (`any`) are skipped so they don't match every struct.
|
|
781
802
|
*/
|
|
782
|
-
function goImplementsEdges(queries) {
|
|
803
|
+
async function goImplementsEdges(queries, onYield) {
|
|
804
|
+
let scanned255 = 0;
|
|
783
805
|
const edges = [];
|
|
784
806
|
const seen = new Set();
|
|
785
807
|
const methodNameSet = (id) => new Set(queries
|
|
@@ -787,11 +809,24 @@ function goImplementsEdges(queries) {
|
|
|
787
809
|
.map((e) => queries.getNodeById(e.target))
|
|
788
810
|
.filter((n) => !!n && n.kind === 'method')
|
|
789
811
|
.map((n) => n.name));
|
|
790
|
-
|
|
812
|
+
// Materializes GO structs only (the pass is language-gated by the caller),
|
|
813
|
+
// never the whole struct kind — that array is O(nodes) on struct-heavy
|
|
814
|
+
// repos like the Linux kernel (#1212).
|
|
815
|
+
const goStructs = [];
|
|
816
|
+
for (const s of queries.iterateNodesByKind('struct')) {
|
|
817
|
+
if ((++scanned255 & 63) === 0)
|
|
818
|
+
await onYield();
|
|
819
|
+
if (s.language === 'go')
|
|
820
|
+
goStructs.push(s);
|
|
821
|
+
}
|
|
791
822
|
const structMethods = new Map();
|
|
792
823
|
for (const s of goStructs)
|
|
793
824
|
structMethods.set(s.id, methodNameSet(s.id));
|
|
794
|
-
for (const iface of queries.
|
|
825
|
+
for (const iface of queries.iterateNodesByKind('interface')) {
|
|
826
|
+
if ((++scanned255 & 63) === 0)
|
|
827
|
+
await onYield();
|
|
828
|
+
if ((++scanned255 & 63) === 0)
|
|
829
|
+
await onYield();
|
|
795
830
|
if (iface.language !== 'go')
|
|
796
831
|
continue;
|
|
797
832
|
const want = methodNameSet(iface.id);
|
|
@@ -851,7 +886,8 @@ function goImplementsEdges(queries) {
|
|
|
851
886
|
* matching the same-file edges extraction already emits). Skips methods that
|
|
852
887
|
* already have a type parent (the same-file case). (#583, cross-file half)
|
|
853
888
|
*/
|
|
854
|
-
function goCrossFileMethodContainsEdges(queries) {
|
|
889
|
+
async function goCrossFileMethodContainsEdges(queries, onYield) {
|
|
890
|
+
let scanned255 = 0;
|
|
855
891
|
const edges = [];
|
|
856
892
|
const seen = new Set();
|
|
857
893
|
const TYPE_KINDS = new Set(['struct', 'class', 'interface', 'enum', 'type_alias']);
|
|
@@ -859,7 +895,11 @@ function goCrossFileMethodContainsEdges(queries) {
|
|
|
859
895
|
const i = p.replace(/\\/g, '/').lastIndexOf('/');
|
|
860
896
|
return i >= 0 ? p.slice(0, i) : '';
|
|
861
897
|
};
|
|
862
|
-
for (const method of queries.
|
|
898
|
+
for (const method of queries.iterateNodesByKind('method')) {
|
|
899
|
+
if ((++scanned255 & 63) === 0)
|
|
900
|
+
await onYield();
|
|
901
|
+
if ((++scanned255 & 63) === 0)
|
|
902
|
+
await onYield();
|
|
863
903
|
if (method.language !== 'go')
|
|
864
904
|
continue;
|
|
865
905
|
// The receiver type is encoded in the method's qualifiedName as `Recv::name`
|
|
@@ -929,13 +969,20 @@ const KMP_TYPE_KINDS = new Set(['class', 'interface', 'struct', 'enum', 'type_al
|
|
|
929
969
|
function kmpKindsCompatible(a, b) {
|
|
930
970
|
return a === b || (KMP_TYPE_KINDS.has(a) && KMP_TYPE_KINDS.has(b));
|
|
931
971
|
}
|
|
932
|
-
function kotlinExpectActualEdges(queries) {
|
|
972
|
+
async function kotlinExpectActualEdges(queries, onYield) {
|
|
973
|
+
let scanned255 = 0;
|
|
933
974
|
const edges = [];
|
|
934
975
|
const seen = new Set();
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
976
|
+
// SQL-side language+decorator pre-filter, streamed. The old
|
|
977
|
+
// `getAllNodes().filter(...)` hydrated the ENTIRE node table into one array
|
|
978
|
+
// just to find kotlin `actual` declarations — on a 2M-node graph that alone
|
|
979
|
+
// exceeded Node's default heap and killed the index (#1212). The LIKE
|
|
980
|
+
// pre-filter can over-match (substring), so the exact decorator check stays.
|
|
981
|
+
for (const act of queries.iterateNodesByLanguageWithDecorator('kotlin', 'actual')) {
|
|
982
|
+
if ((++scanned255 & 63) === 0)
|
|
983
|
+
await onYield();
|
|
984
|
+
if (!act.decorators?.includes('actual'))
|
|
985
|
+
continue;
|
|
939
986
|
let added = 0;
|
|
940
987
|
for (const cand of queries.getNodesByQualifiedNameExact(act.qualifiedName)) {
|
|
941
988
|
if (added >= MAX_CALLBACKS_PER_CHANNEL)
|
|
@@ -969,7 +1016,8 @@ function kotlinExpectActualEdges(queries) {
|
|
|
969
1016
|
}
|
|
970
1017
|
return edges;
|
|
971
1018
|
}
|
|
972
|
-
function interfaceOverrideEdges(queries) {
|
|
1019
|
+
async function interfaceOverrideEdges(queries, onYield) {
|
|
1020
|
+
let scanned255 = 0;
|
|
973
1021
|
const edges = [];
|
|
974
1022
|
const seen = new Set();
|
|
975
1023
|
const methodsOf = (classId) => queries
|
|
@@ -981,7 +1029,9 @@ function interfaceOverrideEdges(queries) {
|
|
|
981
1029
|
// types that conform to protocols. Iterate both.
|
|
982
1030
|
const concreteKinds = ['class', 'struct'];
|
|
983
1031
|
for (const kind of concreteKinds) {
|
|
984
|
-
for (const cls of queries.
|
|
1032
|
+
for (const cls of queries.iterateNodesByKind(kind)) {
|
|
1033
|
+
if ((++scanned255 & 63) === 0)
|
|
1034
|
+
await onYield();
|
|
985
1035
|
const implMethods = methodsOf(cls.id).filter((n) => IFACE_OVERRIDE_LANGS.has(n.language));
|
|
986
1036
|
if (implMethods.length === 0)
|
|
987
1037
|
continue;
|
|
@@ -1055,7 +1105,8 @@ function interfaceOverrideEdges(queries) {
|
|
|
1055
1105
|
* Provenance: `heuristic`, `synthesizedBy: 'go-grpc-stub-impl'`. The
|
|
1056
1106
|
* stub's source line is the wiring site shown in the trace trail.
|
|
1057
1107
|
*/
|
|
1058
|
-
function goGrpcStubImplEdges(queries) {
|
|
1108
|
+
async function goGrpcStubImplEdges(queries, onYield) {
|
|
1109
|
+
let scanned255 = 0;
|
|
1059
1110
|
const edges = [];
|
|
1060
1111
|
const seen = new Set();
|
|
1061
1112
|
const STUB_RE = /^Unimplemented.*Server$/;
|
|
@@ -1067,7 +1118,9 @@ function goGrpcStubImplEdges(queries) {
|
|
|
1067
1118
|
const methodNamesByStruct = new Map();
|
|
1068
1119
|
const methodNodesByStruct = new Map();
|
|
1069
1120
|
const goStructs = [];
|
|
1070
|
-
for (const s of queries.
|
|
1121
|
+
for (const s of queries.iterateNodesByKind('struct')) {
|
|
1122
|
+
if ((++scanned255 & 63) === 0)
|
|
1123
|
+
await onYield();
|
|
1071
1124
|
if (s.language !== 'go')
|
|
1072
1125
|
continue;
|
|
1073
1126
|
goStructs.push(s);
|
|
@@ -1151,11 +1204,14 @@ function goGrpcStubImplEdges(queries) {
|
|
|
1151
1204
|
* (or nothing) and are dropped.
|
|
1152
1205
|
*/
|
|
1153
1206
|
async function reactJsxChildEdges(ctx, onYield) {
|
|
1207
|
+
let scannedFiles = 0;
|
|
1154
1208
|
const edges = [];
|
|
1155
1209
|
const seen = new Set();
|
|
1156
1210
|
const PARENT_KINDS = new Set(['method', 'function', 'component']);
|
|
1157
1211
|
let scanned = 0;
|
|
1158
1212
|
for (const file of ctx.getAllFiles()) {
|
|
1213
|
+
if ((++scannedFiles & 15) === 0)
|
|
1214
|
+
await onYield();
|
|
1159
1215
|
if ((++scanned & 255) === 0)
|
|
1160
1216
|
await onYield(); // #1091: yield mid-scan on huge graphs
|
|
1161
1217
|
const content = ctx.readFile(file);
|
|
@@ -1205,7 +1261,9 @@ async function reactJsxChildEdges(ctx, onYield) {
|
|
|
1205
1261
|
* component, handler→function/method) keeps precision; inline arrows / `$emit`
|
|
1206
1262
|
* skipped.
|
|
1207
1263
|
*/
|
|
1208
|
-
function vueTemplateEdges(ctx) {
|
|
1264
|
+
async function vueTemplateEdges(ctx, onYield) {
|
|
1265
|
+
let scannedFiles = 0;
|
|
1266
|
+
let scanned255 = 0;
|
|
1209
1267
|
const edges = [];
|
|
1210
1268
|
const seen = new Set();
|
|
1211
1269
|
const COMPONENT_KINDS = new Set(['component', 'function', 'class']);
|
|
@@ -1219,12 +1277,16 @@ function vueTemplateEdges(ctx) {
|
|
|
1219
1277
|
// misses it (flat components match by basename and don't need this). Map each
|
|
1220
1278
|
// nested component's Nuxt name → node so those template usages resolve.
|
|
1221
1279
|
const nuxtComponents = new Map();
|
|
1222
|
-
for (const c of ctx.getNodesByKind('component')) {
|
|
1280
|
+
for (const c of (ctx.iterateNodesByKind?.('component') ?? ctx.getNodesByKind('component'))) {
|
|
1281
|
+
if ((++scanned255 & 63) === 0)
|
|
1282
|
+
await onYield();
|
|
1223
1283
|
const nn = nuxtComponentName(c.filePath);
|
|
1224
1284
|
if (nn && !nuxtComponents.has(nn))
|
|
1225
1285
|
nuxtComponents.set(nn, c);
|
|
1226
1286
|
}
|
|
1227
1287
|
for (const file of ctx.getAllFiles()) {
|
|
1288
|
+
if ((++scannedFiles & 15) === 0)
|
|
1289
|
+
await onYield();
|
|
1228
1290
|
if (!file.endsWith('.vue'))
|
|
1229
1291
|
continue;
|
|
1230
1292
|
const content = ctx.readFile(file);
|
|
@@ -1358,13 +1420,16 @@ const RN_JVM_EMIT_RE = /\.emit\s*\(\s*"([^"]+)"\s*,/g;
|
|
|
1358
1420
|
// statement and stops at a block boundary, so the wrapper DEFINITION (whose `(`
|
|
1359
1421
|
// is followed by `… ) {`) never matches. Multi-line tolerant. (java/kotlin/swift)
|
|
1360
1422
|
const RN_NATIVE_SENDEVENT_RE = /\bsendEvent\s*\([^;{}]*?"([^"]+)"/g;
|
|
1361
|
-
function rnEventEdges(ctx) {
|
|
1423
|
+
async function rnEventEdges(ctx, onYield) {
|
|
1424
|
+
let scannedFiles = 0;
|
|
1362
1425
|
// Native dispatchers (source = the native method whose body sends the
|
|
1363
1426
|
// event) and JS handlers (target = the function/method registered as
|
|
1364
1427
|
// the listener) keyed by event name.
|
|
1365
1428
|
const nativeDispatchersByEvent = new Map();
|
|
1366
1429
|
const jsHandlersByEvent = new Map();
|
|
1367
1430
|
for (const file of ctx.getAllFiles()) {
|
|
1431
|
+
if ((++scannedFiles & 15) === 0)
|
|
1432
|
+
await onYield();
|
|
1368
1433
|
const content = ctx.readFile(file);
|
|
1369
1434
|
if (!content)
|
|
1370
1435
|
continue;
|
|
@@ -1552,11 +1617,14 @@ const FABRIC_NATIVE_SUFFIXES = ['', 'View', 'ViewManager', 'ComponentView', 'Man
|
|
|
1552
1617
|
* caller. The Expo method nodes are id-prefixed `expo-module:` and qualified
|
|
1553
1618
|
* `<file>::<module>.<method>` by the framework extractor.
|
|
1554
1619
|
*/
|
|
1555
|
-
function expoCrossPlatformEdges(queries) {
|
|
1620
|
+
async function expoCrossPlatformEdges(queries, onYield) {
|
|
1621
|
+
let scanned255 = 0;
|
|
1556
1622
|
const edges = [];
|
|
1557
1623
|
const seen = new Set();
|
|
1558
1624
|
const byKey = new Map();
|
|
1559
|
-
for (const m of queries.
|
|
1625
|
+
for (const m of queries.iterateNodesByKind('method')) {
|
|
1626
|
+
if ((++scanned255 & 63) === 0)
|
|
1627
|
+
await onYield();
|
|
1560
1628
|
if (!m.id.startsWith('expo-module:'))
|
|
1561
1629
|
continue;
|
|
1562
1630
|
const key = m.qualifiedName.split('::').pop(); // `<module>.<method>`
|
|
@@ -1605,7 +1673,8 @@ function expoCrossPlatformEdges(queries) {
|
|
|
1605
1673
|
* `getFreeDiskStorage`) — that's the JS-visible name, and how the iOS selector
|
|
1606
1674
|
* lines up with the bare Android method name.
|
|
1607
1675
|
*/
|
|
1608
|
-
function rnCrossPlatformEdges(queries) {
|
|
1676
|
+
async function rnCrossPlatformEdges(queries, onYield) {
|
|
1677
|
+
let scanned255 = 0;
|
|
1609
1678
|
const edges = [];
|
|
1610
1679
|
const seen = new Set();
|
|
1611
1680
|
const NATIVE = new Set(['java', 'kotlin', 'objc', 'cpp']);
|
|
@@ -1627,6 +1696,8 @@ function rnCrossPlatformEdges(queries) {
|
|
|
1627
1696
|
// below only runs for genuine cross-platform candidates.
|
|
1628
1697
|
const byName = new Map();
|
|
1629
1698
|
for (const m of queries.iterateNodesByKind('method')) {
|
|
1699
|
+
if ((++scanned255 & 63) === 0)
|
|
1700
|
+
await onYield();
|
|
1630
1701
|
if (!NATIVE.has(m.language))
|
|
1631
1702
|
continue;
|
|
1632
1703
|
const key = norm(m.name);
|
|
@@ -1677,17 +1748,27 @@ function rnCrossPlatformEdges(queries) {
|
|
|
1677
1748
|
}
|
|
1678
1749
|
return edges;
|
|
1679
1750
|
}
|
|
1680
|
-
function fabricNativeImplEdges(ctx) {
|
|
1751
|
+
async function fabricNativeImplEdges(ctx, onYield) {
|
|
1752
|
+
let scanned255 = 0;
|
|
1681
1753
|
const edges = [];
|
|
1682
1754
|
const seen = new Set();
|
|
1683
1755
|
// The Fabric extractor IDs are prefixed `fabric-component:` so we can
|
|
1684
|
-
// filter to just those
|
|
1685
|
-
|
|
1756
|
+
// filter to just those while streaming — never materializing the whole
|
|
1757
|
+
// `component` kind (#1212).
|
|
1758
|
+
const components = [];
|
|
1759
|
+
for (const n of (ctx.iterateNodesByKind?.('component') ?? ctx.getNodesByKind('component'))) {
|
|
1760
|
+
if ((++scanned255 & 63) === 0)
|
|
1761
|
+
await onYield();
|
|
1762
|
+
if (n.id.startsWith('fabric-component:'))
|
|
1763
|
+
components.push(n);
|
|
1764
|
+
}
|
|
1686
1765
|
if (components.length === 0)
|
|
1687
1766
|
return edges;
|
|
1688
1767
|
// Pre-index native classes by name for O(1) lookup.
|
|
1689
1768
|
const nativeClassesByName = new Map();
|
|
1690
|
-
for (const n of ctx.getNodesByKind('class')) {
|
|
1769
|
+
for (const n of (ctx.iterateNodesByKind?.('class') ?? ctx.getNodesByKind('class'))) {
|
|
1770
|
+
if ((++scanned255 & 63) === 0)
|
|
1771
|
+
await onYield();
|
|
1691
1772
|
if (n.language !== 'objc' && n.language !== 'kotlin' && n.language !== 'java' && n.language !== 'cpp')
|
|
1692
1773
|
continue;
|
|
1693
1774
|
const arr = nativeClassesByName.get(n.name);
|
|
@@ -1740,12 +1821,15 @@ function fabricNativeImplEdges(ctx) {
|
|
|
1740
1821
|
* same simple name) are dropped. We need-not bridge by package because Java
|
|
1741
1822
|
* mapper interfaces are typically uniquely named within a project.
|
|
1742
1823
|
*/
|
|
1743
|
-
function mybatisJavaXmlEdges(queries) {
|
|
1824
|
+
async function mybatisJavaXmlEdges(queries, onYield) {
|
|
1825
|
+
let scanned255 = 0;
|
|
1744
1826
|
const edges = [];
|
|
1745
1827
|
const seen = new Set();
|
|
1746
1828
|
// Index Java methods by `<ClassName>::<methodName>` for O(1) lookup.
|
|
1747
1829
|
const javaIndex = new Map();
|
|
1748
1830
|
for (const m of queries.iterateNodesByKind('method')) {
|
|
1831
|
+
if ((++scanned255 & 63) === 0)
|
|
1832
|
+
await onYield();
|
|
1749
1833
|
if (m.language !== 'java' && m.language !== 'kotlin')
|
|
1750
1834
|
continue;
|
|
1751
1835
|
const parts = m.qualifiedName.split('::');
|
|
@@ -1761,6 +1845,8 @@ function mybatisJavaXmlEdges(queries) {
|
|
|
1761
1845
|
javaIndex.set(key, [m]);
|
|
1762
1846
|
}
|
|
1763
1847
|
for (const xml of queries.iterateNodesByKind('method')) {
|
|
1848
|
+
if ((++scanned255 & 63) === 0)
|
|
1849
|
+
await onYield();
|
|
1764
1850
|
if (xml.language !== 'xml')
|
|
1765
1851
|
continue;
|
|
1766
1852
|
// Qualified name: `<namespace>::<id>`. Extract the simple class name.
|
|
@@ -1874,10 +1960,14 @@ function goHandlerIdent(expr) {
|
|
|
1874
1960
|
const m = cleaned.match(/(?:\.|^)([A-Za-z_]\w*)$/);
|
|
1875
1961
|
return m ? m[1] : null;
|
|
1876
1962
|
}
|
|
1877
|
-
function ginMiddlewareChainEdges(queries, ctx) {
|
|
1963
|
+
async function ginMiddlewareChainEdges(queries, ctx, onYield) {
|
|
1964
|
+
let scanned255 = 0;
|
|
1965
|
+
let scannedFiles = 0;
|
|
1878
1966
|
// 1. Find the chain dispatcher(s): a Go method that invokes a `handlers` slice by index.
|
|
1879
1967
|
const dispatchers = [];
|
|
1880
1968
|
for (const n of queries.iterateNodesByKind('method')) {
|
|
1969
|
+
if ((++scanned255 & 63) === 0)
|
|
1970
|
+
await onYield();
|
|
1881
1971
|
if (n.language !== 'go')
|
|
1882
1972
|
continue;
|
|
1883
1973
|
const content = ctx.readFile(n.filePath);
|
|
@@ -1892,6 +1982,8 @@ function ginMiddlewareChainEdges(queries, ctx) {
|
|
|
1892
1982
|
// closures are dropped by goHandlerIdent; the rest are HandlerFuncs.
|
|
1893
1983
|
const registered = new Map(); // name → registeredAt (file:line)
|
|
1894
1984
|
for (const file of ctx.getAllFiles()) {
|
|
1985
|
+
if ((++scannedFiles & 15) === 0)
|
|
1986
|
+
await onYield();
|
|
1895
1987
|
if (!file.endsWith('.go'))
|
|
1896
1988
|
continue;
|
|
1897
1989
|
const content = ctx.readFile(file);
|
|
@@ -1947,10 +2039,13 @@ function ginMiddlewareChainEdges(queries, ctx) {
|
|
|
1947
2039
|
* clause. Link the unit → its form so a `.dfm`/`.fmx` used only as a form
|
|
1948
2040
|
* definition isn't orphaned, and editing the form surfaces its code-behind unit.
|
|
1949
2041
|
*/
|
|
1950
|
-
function pascalFormEdges(ctx) {
|
|
2042
|
+
async function pascalFormEdges(ctx, onYield) {
|
|
2043
|
+
let scannedFiles = 0;
|
|
1951
2044
|
const edges = [];
|
|
1952
2045
|
const allFiles = new Set(ctx.getAllFiles());
|
|
1953
2046
|
for (const file of allFiles) {
|
|
2047
|
+
if ((++scannedFiles & 255) === 0)
|
|
2048
|
+
await onYield();
|
|
1954
2049
|
if (!/\.(dfm|fmx)$/i.test(file))
|
|
1955
2050
|
continue;
|
|
1956
2051
|
const pasFile = file.replace(/\.(dfm|fmx)$/i, '.pas');
|
|
@@ -1986,12 +2081,15 @@ function pascalFormEdges(ctx) {
|
|
|
1986
2081
|
* a loader's data shows the page it feeds) and the page's dependencies include
|
|
1987
2082
|
* its loader.
|
|
1988
2083
|
*/
|
|
1989
|
-
function svelteKitLoadEdges(ctx) {
|
|
2084
|
+
async function svelteKitLoadEdges(ctx, onYield) {
|
|
2085
|
+
let scannedFiles = 0;
|
|
1990
2086
|
const edges = [];
|
|
1991
2087
|
const allFiles = new Set(ctx.getAllFiles());
|
|
1992
2088
|
const HOOKS = new Set(['load', 'actions']);
|
|
1993
2089
|
const HOOK_KINDS = new Set(['function', 'method', 'constant', 'variable']);
|
|
1994
2090
|
for (const file of allFiles) {
|
|
2091
|
+
if ((++scannedFiles & 255) === 0)
|
|
2092
|
+
await onYield();
|
|
1995
2093
|
const m = file.match(/(.*\/)(\+(?:page|layout))\.svelte$/);
|
|
1996
2094
|
if (!m)
|
|
1997
2095
|
continue;
|
|
@@ -2040,10 +2138,13 @@ function svelteKitLoadEdges(ctx) {
|
|
|
2040
2138
|
const THUNK_DECL_RE = /create(?:Async)?Thunk/;
|
|
2041
2139
|
const THUNK_DISPATCH_RE = /\bdispatch\s*\(\s*([A-Za-z_]\w*)\s*[(),]/g;
|
|
2042
2140
|
const THUNK_FANOUT_CAP = 24;
|
|
2043
|
-
function reduxThunkEdges(queries, ctx) {
|
|
2141
|
+
async function reduxThunkEdges(queries, ctx, onYield) {
|
|
2142
|
+
let scanned255 = 0;
|
|
2044
2143
|
const edges = [];
|
|
2045
2144
|
const seen = new Set();
|
|
2046
2145
|
for (const node of queries.iterateNodesByKind('constant')) {
|
|
2146
|
+
if ((++scanned255 & 63) === 0)
|
|
2147
|
+
await onYield();
|
|
2047
2148
|
// Cheap gate: the initializer (captured in `signature`) must be a create(Async)Thunk call —
|
|
2048
2149
|
// avoids reading every constant's body on a large repo.
|
|
2049
2150
|
if (!node.signature || !THUNK_DECL_RE.test(node.signature))
|
|
@@ -2175,10 +2276,13 @@ function resolveRegistryHandler(ctx, name, chained) {
|
|
|
2175
2276
|
return cands.find((n) => n.kind === 'method') ?? null;
|
|
2176
2277
|
}
|
|
2177
2278
|
async function objectRegistryEdges(ctx, onYield) {
|
|
2279
|
+
let scannedFiles = 0;
|
|
2178
2280
|
const edges = [];
|
|
2179
2281
|
const seen = new Set();
|
|
2180
2282
|
let scanned = 0;
|
|
2181
2283
|
for (const file of ctx.getAllFiles()) {
|
|
2284
|
+
if ((++scannedFiles & 15) === 0)
|
|
2285
|
+
await onYield();
|
|
2182
2286
|
if ((++scanned & 255) === 0)
|
|
2183
2287
|
await onYield(); // #1091: yield mid-scan on huge graphs
|
|
2184
2288
|
if (!REGISTRY_JS_EXT.test(file))
|
|
@@ -2290,10 +2394,13 @@ function rtkEndpointNameFromHook(hook) {
|
|
|
2290
2394
|
return null;
|
|
2291
2395
|
return mid.charAt(0).toLowerCase() + mid.slice(1);
|
|
2292
2396
|
}
|
|
2293
|
-
function rtkQueryEdges(queries, ctx) {
|
|
2397
|
+
async function rtkQueryEdges(queries, ctx, onYield) {
|
|
2398
|
+
let scanned255 = 0;
|
|
2294
2399
|
const edges = [];
|
|
2295
2400
|
const seen = new Set();
|
|
2296
2401
|
for (const hook of queries.iterateNodesByKind('function')) {
|
|
2402
|
+
if ((++scanned255 & 63) === 0)
|
|
2403
|
+
await onYield();
|
|
2297
2404
|
// Only our extracted generated-hook bindings (sentinel) — not a real hook fn.
|
|
2298
2405
|
if (hook.signature !== RTK_GENERATED_HOOK_SIGNATURE)
|
|
2299
2406
|
continue;
|
|
@@ -2337,10 +2444,13 @@ const PINIA_FACTORY_RE = /\b(?:export\s+)?const\s+(\w+)\s*=\s*defineStore\s*\(/g
|
|
|
2337
2444
|
const PINIA_BIND_RE = /\bconst\s+(\w+)\s*=\s*(?:await\s+)?(\w+)\s*\(/g;
|
|
2338
2445
|
const PINIA_CALL_RE = /(\w+)\s*\.\s*(\w+)\s*\(/g;
|
|
2339
2446
|
const PINIA_FANOUT_CAP = 80;
|
|
2340
|
-
function piniaStoreEdges(ctx) {
|
|
2447
|
+
async function piniaStoreEdges(ctx, onYield) {
|
|
2448
|
+
let scannedFiles = 0;
|
|
2341
2449
|
// 1. Map each `const useXStore = defineStore(...)` factory → its store file.
|
|
2342
2450
|
const factoryFile = new Map();
|
|
2343
2451
|
for (const file of ctx.getAllFiles()) {
|
|
2452
|
+
if ((++scannedFiles & 15) === 0)
|
|
2453
|
+
await onYield();
|
|
2344
2454
|
if (!PINIA_CONSUMER_EXT.test(file))
|
|
2345
2455
|
continue;
|
|
2346
2456
|
const content = ctx.readFile(file);
|
|
@@ -2356,6 +2466,8 @@ function piniaStoreEdges(ctx) {
|
|
|
2356
2466
|
const edges = [];
|
|
2357
2467
|
const seen = new Set();
|
|
2358
2468
|
for (const file of ctx.getAllFiles()) {
|
|
2469
|
+
if ((++scannedFiles & 15) === 0)
|
|
2470
|
+
await onYield();
|
|
2359
2471
|
if (!PINIA_CONSUMER_EXT.test(file))
|
|
2360
2472
|
continue;
|
|
2361
2473
|
const content = ctx.readFile(file);
|
|
@@ -2428,7 +2540,8 @@ const VUEX_FANOUT_CAP = 120;
|
|
|
2428
2540
|
function pathHasSegment(filePath, seg) {
|
|
2429
2541
|
return new RegExp('[\\\\/]' + seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[\\\\/.]').test(filePath);
|
|
2430
2542
|
}
|
|
2431
|
-
function vuexDispatchEdges(ctx) {
|
|
2543
|
+
async function vuexDispatchEdges(ctx, onYield) {
|
|
2544
|
+
let scannedFiles = 0;
|
|
2432
2545
|
const storeFileCache = new Map();
|
|
2433
2546
|
const isStoreFile = (file) => {
|
|
2434
2547
|
let v = storeFileCache.get(file);
|
|
@@ -2466,6 +2579,8 @@ function vuexDispatchEdges(ctx) {
|
|
|
2466
2579
|
const edges = [];
|
|
2467
2580
|
const seen = new Set();
|
|
2468
2581
|
for (const file of ctx.getAllFiles()) {
|
|
2582
|
+
if ((++scannedFiles & 15) === 0)
|
|
2583
|
+
await onYield();
|
|
2469
2584
|
if (!PINIA_CONSUMER_EXT.test(file))
|
|
2470
2585
|
continue;
|
|
2471
2586
|
const content = ctx.readFile(file);
|
|
@@ -2525,7 +2640,8 @@ const CELERY_TASK_DECORATOR_RE = /@\s*(?:[A-Za-z_][\w.]*\.)?(?:shared_task|task)
|
|
|
2525
2640
|
const CELERY_PY_EXT = /\.py$/;
|
|
2526
2641
|
const CELERY_FANOUT_CAP = 80;
|
|
2527
2642
|
const CELERY_DECORATOR_LOOKBACK = 12; // max lines above a `def` to scan for its decorators
|
|
2528
|
-
function celeryDispatchEdges(ctx) {
|
|
2643
|
+
async function celeryDispatchEdges(ctx, onYield) {
|
|
2644
|
+
let scannedFiles = 0;
|
|
2529
2645
|
// Memoize the decorator check per task-candidate node: it reads the file and scans a few
|
|
2530
2646
|
// lines above the def. Only called on names that are actually `.delay`/`.apply_async`
|
|
2531
2647
|
// receivers, so the candidate set stays small.
|
|
@@ -2569,6 +2685,8 @@ function celeryDispatchEdges(ctx) {
|
|
|
2569
2685
|
const edges = [];
|
|
2570
2686
|
const seen = new Set();
|
|
2571
2687
|
for (const file of ctx.getAllFiles()) {
|
|
2688
|
+
if ((++scannedFiles & 15) === 0)
|
|
2689
|
+
await onYield();
|
|
2572
2690
|
if (!CELERY_PY_EXT.test(file))
|
|
2573
2691
|
continue;
|
|
2574
2692
|
const content = ctx.readFile(file);
|
|
@@ -2647,15 +2765,24 @@ function springFirstParamType(sig) {
|
|
|
2647
2765
|
const type = toks[toks.length - 2].replace(/<.*$/, ''); // drop generic args
|
|
2648
2766
|
return /^[A-Z][A-Za-z0-9_]*$/.test(type) ? type : null;
|
|
2649
2767
|
}
|
|
2650
|
-
function springEventEdges(ctx) {
|
|
2768
|
+
async function springEventEdges(ctx, onYield) {
|
|
2769
|
+
let scannedFiles = 0;
|
|
2651
2770
|
// Pass 1 — event-type → listener methods, scanning only event-relevant files.
|
|
2771
|
+
// This is the ONLY full read sweep: publisher files are recorded here so
|
|
2772
|
+
// pass 2 re-reads just those instead of every .java file again (#1212 —
|
|
2773
|
+
// the double full-repo read was one of the tail's longest unyielded spans).
|
|
2652
2774
|
const listeners = new Map();
|
|
2775
|
+
const publisherFiles = [];
|
|
2653
2776
|
for (const file of ctx.getAllFiles()) {
|
|
2777
|
+
if ((++scannedFiles & 15) === 0)
|
|
2778
|
+
await onYield();
|
|
2654
2779
|
if (!SPRING_JAVA_EXT.test(file))
|
|
2655
2780
|
continue;
|
|
2656
2781
|
const content = ctx.readFile(file);
|
|
2657
2782
|
if (!content)
|
|
2658
2783
|
continue;
|
|
2784
|
+
if (content.includes('.publishEvent('))
|
|
2785
|
+
publisherFiles.push(file);
|
|
2659
2786
|
const hasAnno = content.includes('@EventListener') || content.includes('@TransactionalEventListener');
|
|
2660
2787
|
const hasAppListener = SPRING_APP_LISTENER_RE.test(content);
|
|
2661
2788
|
if (!hasAnno && !hasAppListener)
|
|
@@ -2695,12 +2822,13 @@ function springEventEdges(ctx) {
|
|
|
2695
2822
|
}
|
|
2696
2823
|
if (!listeners.size)
|
|
2697
2824
|
return [];
|
|
2698
|
-
// Pass 2 — link each publishEvent(new XEvent(...)) site → every listener of
|
|
2825
|
+
// Pass 2 — link each publishEvent(new XEvent(...)) site → every listener of
|
|
2826
|
+
// XEvent. Only the publisher files recorded in pass 1 are (re-)read.
|
|
2699
2827
|
const edges = [];
|
|
2700
2828
|
const seen = new Set();
|
|
2701
|
-
for (const file of
|
|
2702
|
-
if (
|
|
2703
|
-
|
|
2829
|
+
for (const file of publisherFiles) {
|
|
2830
|
+
if ((++scannedFiles & 15) === 0)
|
|
2831
|
+
await onYield();
|
|
2704
2832
|
const content = ctx.readFile(file);
|
|
2705
2833
|
if (!content || !content.includes('.publishEvent('))
|
|
2706
2834
|
continue;
|
|
@@ -2785,10 +2913,13 @@ function resolveMediatrArgType(arg, lines, methodStart, dispatchLine) {
|
|
|
2785
2913
|
}
|
|
2786
2914
|
return declType;
|
|
2787
2915
|
}
|
|
2788
|
-
function mediatrDispatchEdges(ctx) {
|
|
2916
|
+
async function mediatrDispatchEdges(ctx, onYield) {
|
|
2917
|
+
let scannedFiles = 0;
|
|
2789
2918
|
// Pass 1 — request/notification type → the Handle method of each handler class.
|
|
2790
2919
|
const handlers = new Map();
|
|
2791
2920
|
for (const file of ctx.getAllFiles()) {
|
|
2921
|
+
if ((++scannedFiles & 15) === 0)
|
|
2922
|
+
await onYield();
|
|
2792
2923
|
if (!MEDIATR_CS_EXT.test(file))
|
|
2793
2924
|
continue;
|
|
2794
2925
|
const content = ctx.readFile(file);
|
|
@@ -2822,6 +2953,8 @@ function mediatrDispatchEdges(ctx) {
|
|
|
2822
2953
|
const edges = [];
|
|
2823
2954
|
const seen = new Set();
|
|
2824
2955
|
for (const file of ctx.getAllFiles()) {
|
|
2956
|
+
if ((++scannedFiles & 15) === 0)
|
|
2957
|
+
await onYield();
|
|
2825
2958
|
if (!MEDIATR_CS_EXT.test(file))
|
|
2826
2959
|
continue;
|
|
2827
2960
|
const content = ctx.readFile(file);
|
|
@@ -2885,7 +3018,8 @@ const SIDEKIQ_DISPATCH_RE = /([A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)\s*\.\
|
|
|
2885
3018
|
const SIDEKIQ_WORKER_RE = /\binclude\s+Sidekiq::(?:Job|Worker)\b/;
|
|
2886
3019
|
const SIDEKIQ_RB_EXT = /\.rb$/;
|
|
2887
3020
|
const SIDEKIQ_FANOUT_CAP = 80;
|
|
2888
|
-
function sidekiqDispatchEdges(ctx) {
|
|
3021
|
+
async function sidekiqDispatchEdges(ctx, onYield) {
|
|
3022
|
+
let scannedFiles = 0;
|
|
2889
3023
|
// class node id → its instance `perform` method (null if the class isn't a Sidekiq worker),
|
|
2890
3024
|
// memoized. Reads the class body for the mixin; only consulted for actual dispatch receivers.
|
|
2891
3025
|
const performCache = new Map();
|
|
@@ -2922,6 +3056,8 @@ function sidekiqDispatchEdges(ctx) {
|
|
|
2922
3056
|
const edges = [];
|
|
2923
3057
|
const seen = new Set();
|
|
2924
3058
|
for (const file of ctx.getAllFiles()) {
|
|
3059
|
+
if ((++scannedFiles & 15) === 0)
|
|
3060
|
+
await onYield();
|
|
2925
3061
|
if (!SIDEKIQ_RB_EXT.test(file))
|
|
2926
3062
|
continue;
|
|
2927
3063
|
const content = ctx.readFile(file);
|
|
@@ -3105,12 +3241,15 @@ function nixLeadingPlainSegments(name) {
|
|
|
3105
3241
|
return segs;
|
|
3106
3242
|
}
|
|
3107
3243
|
async function nixOptionPathEdges(queries, onYield) {
|
|
3244
|
+
let scanned255 = 0;
|
|
3108
3245
|
// One streaming pass over nix bindings (variables + the odd function-valued
|
|
3109
3246
|
// option); memory stays O(bindings-kept), not O(all nodes) (#610).
|
|
3110
3247
|
const byFile = new Map();
|
|
3111
3248
|
let scanned = 0;
|
|
3112
3249
|
for (const kind of ['variable', 'function']) {
|
|
3113
3250
|
for (const node of queries.iterateNodesByKind(kind)) {
|
|
3251
|
+
if ((++scanned255 & 63) === 0)
|
|
3252
|
+
await onYield();
|
|
3114
3253
|
if ((++scanned & 0x3fff) === 0 && onYield)
|
|
3115
3254
|
await onYield();
|
|
3116
3255
|
if (node.language !== 'nix')
|
|
@@ -3223,9 +3362,18 @@ async function nixOptionPathEdges(queries, onYield) {
|
|
|
3223
3362
|
}
|
|
3224
3363
|
return edges;
|
|
3225
3364
|
}
|
|
3226
|
-
function erlangBehaviourDispatchEdges(queries, ctx) {
|
|
3227
|
-
|
|
3228
|
-
|
|
3365
|
+
async function erlangBehaviourDispatchEdges(queries, ctx, onYield) {
|
|
3366
|
+
let scannedFiles = 0;
|
|
3367
|
+
let scanned255 = 0;
|
|
3368
|
+
// Cheap language gate: no Erlang modules → no cost beyond one streamed
|
|
3369
|
+
// kind scan (never a materialized array of every namespace — #1212).
|
|
3370
|
+
const erlangModules = [];
|
|
3371
|
+
for (const n of queries.iterateNodesByKind('namespace')) {
|
|
3372
|
+
if ((++scanned255 & 63) === 0)
|
|
3373
|
+
await onYield();
|
|
3374
|
+
if (n.language === 'erlang')
|
|
3375
|
+
erlangModules.push(n);
|
|
3376
|
+
}
|
|
3229
3377
|
if (erlangModules.length === 0)
|
|
3230
3378
|
return [];
|
|
3231
3379
|
// Pass 1 — scan every Erlang file with `-callback` decls: behaviour module →
|
|
@@ -3239,6 +3387,8 @@ function erlangBehaviourDispatchEdges(queries, ctx) {
|
|
|
3239
3387
|
const declaringBehaviours = new Map(); // `fn/arity` → behaviour namespaces
|
|
3240
3388
|
const callbackNames = new Set();
|
|
3241
3389
|
for (const file of ctx.getAllFiles()) {
|
|
3390
|
+
if ((++scannedFiles & 15) === 0)
|
|
3391
|
+
await onYield();
|
|
3242
3392
|
if (!ERLANG_EXT.test(file))
|
|
3243
3393
|
continue;
|
|
3244
3394
|
const behaviour = moduleByFile.get(file);
|
|
@@ -3297,6 +3447,8 @@ function erlangBehaviourDispatchEdges(queries, ctx) {
|
|
|
3297
3447
|
const edges = [];
|
|
3298
3448
|
const seen = new Set();
|
|
3299
3449
|
for (const file of ctx.getAllFiles()) {
|
|
3450
|
+
if ((++scannedFiles & 15) === 0)
|
|
3451
|
+
await onYield();
|
|
3300
3452
|
if (!ERLANG_EXT.test(file))
|
|
3301
3453
|
continue;
|
|
3302
3454
|
const content = ctx.readFile(file);
|
|
@@ -3395,7 +3547,8 @@ function phpArrayBody(src, openIdx) {
|
|
|
3395
3547
|
}
|
|
3396
3548
|
return null;
|
|
3397
3549
|
}
|
|
3398
|
-
function laravelEventEdges(ctx) {
|
|
3550
|
+
async function laravelEventEdges(ctx, onYield) {
|
|
3551
|
+
let scannedFiles = 0;
|
|
3399
3552
|
// event short name → its listener `handle` methods (deduped by node id).
|
|
3400
3553
|
const listeners = new Map();
|
|
3401
3554
|
const add = (event, handle) => {
|
|
@@ -3412,6 +3565,8 @@ function laravelEventEdges(ctx) {
|
|
|
3412
3565
|
&& n.startLine >= cls.startLine && n.startLine <= (cls.endLine ?? cls.startLine)) ?? null;
|
|
3413
3566
|
// Pass 1 — build the event→handle map from both registration mechanisms.
|
|
3414
3567
|
for (const file of ctx.getAllFiles()) {
|
|
3568
|
+
if ((++scannedFiles & 15) === 0)
|
|
3569
|
+
await onYield();
|
|
3415
3570
|
if (!LARAVEL_PHP_EXT.test(file))
|
|
3416
3571
|
continue;
|
|
3417
3572
|
const content = ctx.readFile(file);
|
|
@@ -3457,6 +3612,8 @@ function laravelEventEdges(ctx) {
|
|
|
3457
3612
|
const edges = [];
|
|
3458
3613
|
const seen = new Set();
|
|
3459
3614
|
for (const file of ctx.getAllFiles()) {
|
|
3615
|
+
if ((++scannedFiles & 15) === 0)
|
|
3616
|
+
await onYield();
|
|
3460
3617
|
if (!LARAVEL_PHP_EXT.test(file))
|
|
3461
3618
|
continue;
|
|
3462
3619
|
const content = ctx.readFile(file);
|
|
@@ -3516,95 +3673,160 @@ async function synthesizeCallbackEdges(queries, ctx) {
|
|
|
3516
3673
|
// that itself hangs (a real wedge) never reaches the next yield, so the
|
|
3517
3674
|
// watchdog still catches that. See ./cooperative-yield.
|
|
3518
3675
|
const yieldToLoop = (0, cooperative_yield_1.createYielder)();
|
|
3676
|
+
// Per-pass wall-clock timing to stderr, opt-in via CODEGRAPH_SYNTH_TIMINGS
|
|
3677
|
+
// (=1: passes over 250ms; =all: every pass). This is the diagnostic that
|
|
3678
|
+
// located both the #1091/#1122 watchdog stalls and the #1212 OOM — keep it.
|
|
3679
|
+
const markT = { t: Date.now() };
|
|
3680
|
+
const __mark = (label) => {
|
|
3681
|
+
const now = Date.now();
|
|
3682
|
+
const dt = now - markT.t;
|
|
3683
|
+
markT.t = now;
|
|
3684
|
+
if (process.env.CODEGRAPH_SYNTH_TIMINGS && (dt > 250 || process.env.CODEGRAPH_SYNTH_TIMINGS === 'all')) {
|
|
3685
|
+
console.error(`[synth-timing] ${label}: ${dt}ms`);
|
|
3686
|
+
}
|
|
3687
|
+
};
|
|
3688
|
+
// Language gating: one indexed DISTINCT over the files table lets a pass
|
|
3689
|
+
// whose own filters reference a specific language/extension be skipped
|
|
3690
|
+
// outright when the project has no such files — its result is provably
|
|
3691
|
+
// empty, so skipping is behavior-identical and the cost drops to zero
|
|
3692
|
+
// (the Kotlin pass was the OOM culprit on the pure-C Linux kernel, #1212).
|
|
3693
|
+
// Passes without an explicit language filter always run.
|
|
3694
|
+
const langs = queries.getDistinctFileLanguages();
|
|
3695
|
+
const has = (...ls) => ls.some((l) => langs.has(l));
|
|
3696
|
+
const JS_FAMILY = ['typescript', 'javascript', 'tsx', 'jsx'];
|
|
3697
|
+
const NONE = [];
|
|
3519
3698
|
// Cross-file Go method→type `contains` edges must be synthesized AND persisted
|
|
3520
3699
|
// FIRST: a method declared in a different file from its receiver type is
|
|
3521
3700
|
// otherwise orphaned from the struct, and goImplementsEdges (next) derives a
|
|
3522
3701
|
// struct's method set from its `contains` edges — so without this it would
|
|
3523
3702
|
// under-count the interfaces a cross-file struct satisfies. (#583)
|
|
3524
|
-
const goMethodContains = goCrossFileMethodContainsEdges(queries);
|
|
3525
|
-
|
|
3526
|
-
queries.insertEdges(goMethodContains);
|
|
3703
|
+
const goMethodContains = has('go') ? await goCrossFileMethodContainsEdges(queries, yieldToLoop) : NONE;
|
|
3704
|
+
for (let i = 0; i < goMethodContains.length; i += 2000) {
|
|
3705
|
+
queries.insertEdges(goMethodContains.slice(i, i + 2000));
|
|
3706
|
+
await yieldToLoop();
|
|
3707
|
+
}
|
|
3527
3708
|
await yieldToLoop();
|
|
3709
|
+
__mark('goMethodContains');
|
|
3528
3710
|
// Go implicit `implements` edges must be synthesized AND persisted next: the
|
|
3529
3711
|
// interface-dispatch bridge below reads `implements` edges from the DB, and
|
|
3530
3712
|
// Go has none statically. (Other languages already have static implements
|
|
3531
3713
|
// edges from extraction, so they don't need this pre-pass.)
|
|
3532
|
-
const goImpl = goImplementsEdges(queries);
|
|
3533
|
-
|
|
3534
|
-
queries.insertEdges(goImpl);
|
|
3714
|
+
const goImpl = has('go') ? await goImplementsEdges(queries, yieldToLoop) : NONE;
|
|
3715
|
+
for (let i = 0; i < goImpl.length; i += 2000) {
|
|
3716
|
+
queries.insertEdges(goImpl.slice(i, i + 2000));
|
|
3717
|
+
await yieldToLoop();
|
|
3718
|
+
}
|
|
3535
3719
|
await yieldToLoop();
|
|
3720
|
+
__mark('goImplements');
|
|
3536
3721
|
const fieldEdges = await fieldChannelEdges(queries, ctx, yieldToLoop);
|
|
3537
3722
|
await yieldToLoop();
|
|
3723
|
+
__mark('fieldEdges');
|
|
3538
3724
|
const closureCollEdges = await closureCollectionEdges(queries, ctx, yieldToLoop);
|
|
3539
3725
|
await yieldToLoop();
|
|
3726
|
+
__mark('closureCollEdges');
|
|
3540
3727
|
const emitterEdges = await eventEmitterEdges(ctx, yieldToLoop);
|
|
3541
3728
|
await yieldToLoop();
|
|
3542
|
-
|
|
3729
|
+
__mark('emitterEdges');
|
|
3730
|
+
const renderEdges = await reactRenderEdges(queries, ctx, yieldToLoop);
|
|
3543
3731
|
await yieldToLoop();
|
|
3732
|
+
__mark('renderEdges');
|
|
3544
3733
|
const jsxEdges = await reactJsxChildEdges(ctx, yieldToLoop);
|
|
3545
3734
|
await yieldToLoop();
|
|
3546
|
-
|
|
3735
|
+
__mark('jsxEdges');
|
|
3736
|
+
const vueEdges = has('vue') ? await vueTemplateEdges(ctx, yieldToLoop) : NONE;
|
|
3547
3737
|
await yieldToLoop();
|
|
3548
|
-
|
|
3738
|
+
__mark('vueEdges');
|
|
3739
|
+
const svelteKitEdges = has('svelte') ? await svelteKitLoadEdges(ctx, yieldToLoop) : NONE;
|
|
3549
3740
|
await yieldToLoop();
|
|
3550
|
-
|
|
3741
|
+
__mark('svelteKitEdges');
|
|
3742
|
+
const pascalEdges = await pascalFormEdges(ctx, yieldToLoop);
|
|
3551
3743
|
await yieldToLoop();
|
|
3552
|
-
|
|
3744
|
+
__mark('pascalEdges');
|
|
3745
|
+
const flutterEdges = has('dart') ? await flutterBuildEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3553
3746
|
await yieldToLoop();
|
|
3554
|
-
|
|
3747
|
+
__mark('flutterEdges');
|
|
3748
|
+
const arkuiStateEdges = has('arkts') ? await arkuiStateBuildEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3555
3749
|
await yieldToLoop();
|
|
3556
|
-
|
|
3750
|
+
__mark('arkuiStateEdges');
|
|
3751
|
+
const arkuiEmitter = has('arkts') ? await arkuiEmitterEdges(ctx, yieldToLoop) : NONE;
|
|
3557
3752
|
await yieldToLoop();
|
|
3558
|
-
|
|
3753
|
+
__mark('arkuiEmitter');
|
|
3754
|
+
const arkuiRoutes = has('arkts') ? await arkuiRouterEdges(ctx, yieldToLoop) : NONE;
|
|
3559
3755
|
await yieldToLoop();
|
|
3560
|
-
|
|
3756
|
+
__mark('arkuiRoutes');
|
|
3757
|
+
const cppEdges = has('cpp') ? await cppOverrideEdges(queries, yieldToLoop) : NONE;
|
|
3561
3758
|
await yieldToLoop();
|
|
3562
|
-
|
|
3759
|
+
__mark('cppEdges');
|
|
3760
|
+
const ifaceEdges = has('java', 'kotlin', 'csharp', 'swift', 'scala', 'go', 'rust', 'arkts', ...JS_FAMILY)
|
|
3761
|
+
? await interfaceOverrideEdges(queries, yieldToLoop) : NONE;
|
|
3563
3762
|
await yieldToLoop();
|
|
3564
|
-
|
|
3763
|
+
__mark('ifaceEdges');
|
|
3764
|
+
const kotlinExpectActual = has('kotlin') ? await kotlinExpectActualEdges(queries, yieldToLoop) : NONE;
|
|
3565
3765
|
await yieldToLoop();
|
|
3566
|
-
|
|
3766
|
+
__mark('kotlinExpectActual');
|
|
3767
|
+
const goGrpcEdges = has('go') ? await goGrpcStubImplEdges(queries, yieldToLoop) : NONE;
|
|
3567
3768
|
await yieldToLoop();
|
|
3568
|
-
|
|
3769
|
+
__mark('goGrpcEdges');
|
|
3770
|
+
const rnEventEdgesList = has(...JS_FAMILY) ? await rnEventEdges(ctx, yieldToLoop) : NONE;
|
|
3569
3771
|
await yieldToLoop();
|
|
3570
|
-
|
|
3772
|
+
__mark('rnEventEdgesList');
|
|
3773
|
+
const fabricNativeEdges = await fabricNativeImplEdges(ctx, yieldToLoop);
|
|
3571
3774
|
await yieldToLoop();
|
|
3572
|
-
|
|
3775
|
+
__mark('fabricNativeEdges');
|
|
3776
|
+
const expoXPlatEdges = await expoCrossPlatformEdges(queries, yieldToLoop);
|
|
3573
3777
|
await yieldToLoop();
|
|
3574
|
-
|
|
3778
|
+
__mark('expoXPlatEdges');
|
|
3779
|
+
const rnXPlatEdges = await rnCrossPlatformEdges(queries, yieldToLoop);
|
|
3575
3780
|
await yieldToLoop();
|
|
3576
|
-
|
|
3781
|
+
__mark('rnXPlatEdges');
|
|
3782
|
+
const mybatisEdges = has('java', 'kotlin') && has('xml') ? await mybatisJavaXmlEdges(queries, yieldToLoop) : NONE;
|
|
3577
3783
|
await yieldToLoop();
|
|
3578
|
-
|
|
3784
|
+
__mark('mybatisEdges');
|
|
3785
|
+
const ginEdges = has('go') ? await ginMiddlewareChainEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3579
3786
|
await yieldToLoop();
|
|
3580
|
-
|
|
3787
|
+
__mark('ginEdges');
|
|
3788
|
+
const thunkEdges = has(...JS_FAMILY) ? await reduxThunkEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3581
3789
|
await yieldToLoop();
|
|
3790
|
+
__mark('thunkEdges');
|
|
3582
3791
|
const registryEdges = await objectRegistryEdges(ctx, yieldToLoop);
|
|
3583
3792
|
await yieldToLoop();
|
|
3584
|
-
|
|
3793
|
+
__mark('registryEdges');
|
|
3794
|
+
const rtkEdges = has(...JS_FAMILY) ? await rtkQueryEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3585
3795
|
await yieldToLoop();
|
|
3586
|
-
|
|
3796
|
+
__mark('rtkEdges');
|
|
3797
|
+
const piniaEdges = has('vue', ...JS_FAMILY) ? await piniaStoreEdges(ctx, yieldToLoop) : NONE;
|
|
3587
3798
|
await yieldToLoop();
|
|
3588
|
-
|
|
3799
|
+
__mark('piniaEdges');
|
|
3800
|
+
const vuexEdges = has('vue', ...JS_FAMILY) ? await vuexDispatchEdges(ctx, yieldToLoop) : NONE;
|
|
3589
3801
|
await yieldToLoop();
|
|
3590
|
-
|
|
3802
|
+
__mark('vuexEdges');
|
|
3803
|
+
const celeryEdges = has('python') ? await celeryDispatchEdges(ctx, yieldToLoop) : NONE;
|
|
3591
3804
|
await yieldToLoop();
|
|
3592
|
-
|
|
3805
|
+
__mark('celeryEdges');
|
|
3806
|
+
const springEdges = has('java') ? await springEventEdges(ctx, yieldToLoop) : NONE;
|
|
3593
3807
|
await yieldToLoop();
|
|
3594
|
-
|
|
3808
|
+
__mark('springEdges');
|
|
3809
|
+
const mediatrEdges = has('csharp') ? await mediatrDispatchEdges(ctx, yieldToLoop) : NONE;
|
|
3595
3810
|
await yieldToLoop();
|
|
3596
|
-
|
|
3811
|
+
__mark('mediatrEdges');
|
|
3812
|
+
const sidekiqEdges = has('ruby') ? await sidekiqDispatchEdges(ctx, yieldToLoop) : NONE;
|
|
3597
3813
|
await yieldToLoop();
|
|
3598
|
-
|
|
3814
|
+
__mark('sidekiqEdges');
|
|
3815
|
+
const erlangBehaviourEdges = has('erlang') ? await erlangBehaviourDispatchEdges(queries, ctx, yieldToLoop) : NONE;
|
|
3599
3816
|
await yieldToLoop();
|
|
3600
|
-
|
|
3817
|
+
__mark('erlangBehaviourEdges');
|
|
3818
|
+
const laravelEdges = has('php') ? await laravelEventEdges(ctx, yieldToLoop) : NONE;
|
|
3601
3819
|
await yieldToLoop();
|
|
3602
|
-
|
|
3820
|
+
__mark('laravelEdges');
|
|
3821
|
+
const cFnPtrEdges = has('c', 'cpp') ? await (0, c_fnptr_synthesizer_1.cFnPointerDispatchEdges)(queries, ctx, yieldToLoop) : NONE;
|
|
3603
3822
|
await yieldToLoop();
|
|
3604
|
-
|
|
3823
|
+
__mark('cFnPtrEdges');
|
|
3824
|
+
const goframeEdges = has('go') ? await (0, goframe_synthesizer_1.goframeRouteEdges)(ctx, yieldToLoop) : NONE;
|
|
3605
3825
|
await yieldToLoop();
|
|
3606
|
-
|
|
3826
|
+
__mark('goframeEdges');
|
|
3827
|
+
const nixOptionEdges = has('nix') ? await nixOptionPathEdges(queries, yieldToLoop) : NONE;
|
|
3607
3828
|
await yieldToLoop();
|
|
3829
|
+
__mark('nixOptionEdges');
|
|
3608
3830
|
const merged = [];
|
|
3609
3831
|
const seen = new Set();
|
|
3610
3832
|
for (const e of [
|
|
@@ -3651,8 +3873,15 @@ async function synthesizeCallbackEdges(queries, ctx) {
|
|
|
3651
3873
|
seen.add(key);
|
|
3652
3874
|
merged.push(e);
|
|
3653
3875
|
}
|
|
3654
|
-
|
|
3655
|
-
|
|
3876
|
+
__mark('dedupe-merge');
|
|
3877
|
+
// Chunked insert with yields: on the Linux kernel the merged synthesized
|
|
3878
|
+
// edge set is ~275k rows, and one transaction for all of them was a 20s
|
|
3879
|
+
// unyielded main-thread span (#1212 follow-up) — the last one in the tail.
|
|
3880
|
+
for (let i = 0; i < merged.length; i += 2000) {
|
|
3881
|
+
queries.insertEdges(merged.slice(i, i + 2000));
|
|
3882
|
+
await yieldToLoop();
|
|
3883
|
+
}
|
|
3884
|
+
__mark('insertMergedEdges');
|
|
3656
3885
|
return merged.length + goImpl.length + goMethodContains.length;
|
|
3657
3886
|
}
|
|
3658
3887
|
//# sourceMappingURL=callback-synthesizer.js.map
|