@colbymchenry/codegraph-win32-arm64 1.1.6 → 1.3.0
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.d.ts +1 -1
- package/lib/dist/bin/codegraph.d.ts.map +1 -1
- package/lib/dist/bin/codegraph.js +232 -61
- package/lib/dist/bin/codegraph.js.map +1 -1
- package/lib/dist/bin/command-supervision.d.ts.map +1 -1
- package/lib/dist/bin/command-supervision.js +14 -1
- package/lib/dist/bin/command-supervision.js.map +1 -1
- package/lib/dist/db/migrations.d.ts +1 -1
- package/lib/dist/db/migrations.d.ts.map +1 -1
- package/lib/dist/db/migrations.js +19 -1
- package/lib/dist/db/migrations.js.map +1 -1
- package/lib/dist/db/queries.d.ts +51 -0
- package/lib/dist/db/queries.d.ts.map +1 -1
- package/lib/dist/db/queries.js +141 -0
- package/lib/dist/db/queries.js.map +1 -1
- package/lib/dist/db/schema.sql +19 -0
- package/lib/dist/directory.d.ts +9 -5
- package/lib/dist/directory.d.ts.map +1 -1
- package/lib/dist/directory.js +210 -19
- package/lib/dist/directory.js.map +1 -1
- package/lib/dist/extraction/cfml-extractor.d.ts +107 -0
- package/lib/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/lib/dist/extraction/cfml-extractor.js +494 -0
- package/lib/dist/extraction/cfml-extractor.js.map +1 -0
- package/lib/dist/extraction/grammars.d.ts +9 -0
- package/lib/dist/extraction/grammars.d.ts.map +1 -1
- package/lib/dist/extraction/grammars.js +115 -3
- package/lib/dist/extraction/grammars.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +46 -1
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +254 -3
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/languages/arkts.d.ts +3 -0
- package/lib/dist/extraction/languages/arkts.d.ts.map +1 -0
- package/lib/dist/extraction/languages/arkts.js +127 -0
- package/lib/dist/extraction/languages/arkts.js.map +1 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts +59 -0
- package/lib/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/lib/dist/extraction/languages/c-cpp.js +499 -3
- package/lib/dist/extraction/languages/c-cpp.js.map +1 -1
- package/lib/dist/extraction/languages/cfquery.d.ts +12 -0
- package/lib/dist/extraction/languages/cfquery.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cfquery.js +28 -0
- package/lib/dist/extraction/languages/cfquery.js.map +1 -0
- package/lib/dist/extraction/languages/cfscript.d.ts +3 -0
- package/lib/dist/extraction/languages/cfscript.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cfscript.js +73 -0
- package/lib/dist/extraction/languages/cfscript.js.map +1 -0
- package/lib/dist/extraction/languages/cobol.d.ts +33 -0
- package/lib/dist/extraction/languages/cobol.d.ts.map +1 -0
- package/lib/dist/extraction/languages/cobol.js +499 -0
- package/lib/dist/extraction/languages/cobol.js.map +1 -0
- package/lib/dist/extraction/languages/erlang.d.ts +3 -0
- package/lib/dist/extraction/languages/erlang.d.ts.map +1 -0
- package/lib/dist/extraction/languages/erlang.js +350 -0
- package/lib/dist/extraction/languages/erlang.js.map +1 -0
- package/lib/dist/extraction/languages/index.d.ts.map +1 -1
- package/lib/dist/extraction/languages/index.js +18 -0
- package/lib/dist/extraction/languages/index.js.map +1 -1
- package/lib/dist/extraction/languages/nix.d.ts +3 -0
- package/lib/dist/extraction/languages/nix.d.ts.map +1 -0
- package/lib/dist/extraction/languages/nix.js +294 -0
- package/lib/dist/extraction/languages/nix.js.map +1 -0
- package/lib/dist/extraction/languages/solidity.d.ts +3 -0
- package/lib/dist/extraction/languages/solidity.d.ts.map +1 -0
- package/lib/dist/extraction/languages/solidity.js +293 -0
- package/lib/dist/extraction/languages/solidity.js.map +1 -0
- package/lib/dist/extraction/languages/terraform.d.ts +3 -0
- package/lib/dist/extraction/languages/terraform.d.ts.map +1 -0
- package/lib/dist/extraction/languages/terraform.js +641 -0
- package/lib/dist/extraction/languages/terraform.js.map +1 -0
- package/lib/dist/extraction/languages/vbnet.d.ts +11 -0
- package/lib/dist/extraction/languages/vbnet.d.ts.map +1 -0
- package/lib/dist/extraction/languages/vbnet.js +141 -0
- package/lib/dist/extraction/languages/vbnet.js.map +1 -0
- package/lib/dist/extraction/mybatis-extractor.d.ts +30 -10
- package/lib/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/lib/dist/extraction/mybatis-extractor.js +140 -38
- package/lib/dist/extraction/mybatis-extractor.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter-helpers.js +1 -0
- package/lib/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts +20 -1
- package/lib/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.d.ts +38 -0
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +845 -16
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/lib/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/lib/dist/graph/traversal.d.ts.map +1 -1
- package/lib/dist/graph/traversal.js +76 -17
- package/lib/dist/graph/traversal.js.map +1 -1
- package/lib/dist/index.d.ts +63 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +310 -6
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +3 -1
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/mcp/daemon.d.ts +25 -3
- package/lib/dist/mcp/daemon.d.ts.map +1 -1
- package/lib/dist/mcp/daemon.js +39 -7
- package/lib/dist/mcp/daemon.js.map +1 -1
- package/lib/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/lib/dist/mcp/dynamic-boundaries.js +2 -1
- package/lib/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/lib/dist/mcp/early-ppid.d.ts +26 -0
- package/lib/dist/mcp/early-ppid.d.ts.map +1 -0
- package/lib/dist/mcp/early-ppid.js +29 -0
- package/lib/dist/mcp/early-ppid.js.map +1 -0
- package/lib/dist/mcp/index.d.ts.map +1 -1
- package/lib/dist/mcp/index.js +21 -4
- package/lib/dist/mcp/index.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +21 -3
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/startup-handshake.d.ts +44 -0
- package/lib/dist/mcp/startup-handshake.d.ts.map +1 -0
- package/lib/dist/mcp/startup-handshake.js +73 -0
- package/lib/dist/mcp/startup-handshake.js.map +1 -0
- package/lib/dist/mcp/tools.d.ts +22 -0
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +74 -9
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/project-config.d.ts +38 -0
- package/lib/dist/project-config.d.ts.map +1 -1
- package/lib/dist/project-config.js +101 -2
- package/lib/dist/project-config.js.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts +1 -1
- package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/lib/dist/resolution/callback-synthesizer.js +743 -11
- package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
- package/lib/dist/resolution/cooperative-yield.d.ts +32 -0
- package/lib/dist/resolution/cooperative-yield.d.ts.map +1 -0
- package/lib/dist/resolution/cooperative-yield.js +42 -0
- package/lib/dist/resolution/cooperative-yield.js.map +1 -0
- package/lib/dist/resolution/frameworks/cics.d.ts +20 -0
- package/lib/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/lib/dist/resolution/frameworks/cics.js +90 -0
- package/lib/dist/resolution/frameworks/cics.js.map +1 -0
- package/lib/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/index.js +6 -0
- package/lib/dist/resolution/frameworks/index.js.map +1 -1
- package/lib/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/lib/dist/resolution/frameworks/java.js +14 -6
- package/lib/dist/resolution/frameworks/java.js.map +1 -1
- package/lib/dist/resolution/frameworks/terraform.d.ts +38 -0
- package/lib/dist/resolution/frameworks/terraform.d.ts.map +1 -0
- package/lib/dist/resolution/frameworks/terraform.js +277 -0
- package/lib/dist/resolution/frameworks/terraform.js.map +1 -0
- package/lib/dist/resolution/import-resolver.d.ts +7 -0
- package/lib/dist/resolution/import-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/import-resolver.js +129 -4
- package/lib/dist/resolution/import-resolver.js.map +1 -1
- package/lib/dist/resolution/index.d.ts +44 -2
- package/lib/dist/resolution/index.d.ts.map +1 -1
- package/lib/dist/resolution/index.js +335 -30
- package/lib/dist/resolution/index.js.map +1 -1
- package/lib/dist/resolution/name-matcher.d.ts +22 -3
- package/lib/dist/resolution/name-matcher.d.ts.map +1 -1
- package/lib/dist/resolution/name-matcher.js +484 -39
- package/lib/dist/resolution/name-matcher.js.map +1 -1
- package/lib/dist/resolution/strip-comments.d.ts +1 -1
- package/lib/dist/resolution/strip-comments.d.ts.map +1 -1
- package/lib/dist/resolution/strip-comments.js +49 -0
- package/lib/dist/resolution/strip-comments.js.map +1 -1
- package/lib/dist/resolution/types.d.ts +20 -0
- package/lib/dist/resolution/types.d.ts.map +1 -1
- package/lib/dist/resolution/workspace-packages.d.ts +10 -0
- package/lib/dist/resolution/workspace-packages.d.ts.map +1 -1
- package/lib/dist/resolution/workspace-packages.js +142 -4
- package/lib/dist/resolution/workspace-packages.js.map +1 -1
- package/lib/dist/search/identifier-segments.d.ts +60 -0
- package/lib/dist/search/identifier-segments.d.ts.map +1 -0
- package/lib/dist/search/identifier-segments.js +176 -0
- package/lib/dist/search/identifier-segments.js.map +1 -0
- 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/watcher.d.ts +10 -5
- package/lib/dist/sync/watcher.d.ts.map +1 -1
- package/lib/dist/sync/watcher.js +51 -14
- package/lib/dist/sync/watcher.js.map +1 -1
- package/lib/dist/sync/worktree.d.ts.map +1 -1
- package/lib/dist/sync/worktree.js +5 -0
- package/lib/dist/sync/worktree.js.map +1 -1
- package/lib/dist/types.d.ts +19 -1
- package/lib/dist/types.d.ts.map +1 -1
- package/lib/dist/types.js +10 -0
- package/lib/dist/types.js.map +1 -1
- package/lib/dist/upgrade/index.js +1 -1
- package/lib/dist/upgrade/index.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/reasoning/config.d.ts +0 -45
- package/lib/dist/reasoning/config.d.ts.map +0 -1
- package/lib/dist/reasoning/config.js +0 -171
- package/lib/dist/reasoning/config.js.map +0 -1
- package/lib/dist/reasoning/credentials.d.ts +0 -5
- package/lib/dist/reasoning/credentials.d.ts.map +0 -1
- package/lib/dist/reasoning/credentials.js +0 -83
- package/lib/dist/reasoning/credentials.js.map +0 -1
- package/lib/dist/reasoning/login.d.ts +0 -21
- package/lib/dist/reasoning/login.d.ts.map +0 -1
- package/lib/dist/reasoning/login.js +0 -85
- package/lib/dist/reasoning/login.js.map +0 -1
- package/lib/dist/reasoning/reasoner.d.ts +0 -43
- package/lib/dist/reasoning/reasoner.d.ts.map +0 -1
- package/lib/dist/reasoning/reasoner.js +0 -308
- package/lib/dist/reasoning/reasoner.js.map +0 -1
|
@@ -5,6 +5,7 @@ const generated_detection_1 = require("../extraction/generated-detection");
|
|
|
5
5
|
const strip_comments_1 = require("./strip-comments");
|
|
6
6
|
const c_fnptr_synthesizer_1 = require("./c-fnptr-synthesizer");
|
|
7
7
|
const goframe_synthesizer_1 = require("./goframe-synthesizer");
|
|
8
|
+
const cooperative_yield_1 = require("./cooperative-yield");
|
|
8
9
|
const REGISTRAR_NAME = /^(on[A-Z]\w*|subscribe|addListener|addEventListener|register|watch|listen|addCallback)$/;
|
|
9
10
|
const DISPATCHER_NAME = /(emit|trigger|notify|dispatch|fire|publish|flush)/i;
|
|
10
11
|
const MAX_CALLBACKS_PER_CHANNEL = 40;
|
|
@@ -113,10 +114,13 @@ function* methodAndFunctionNodes(queries) {
|
|
|
113
114
|
yield* queries.iterateNodesByKind('function');
|
|
114
115
|
}
|
|
115
116
|
/** Phase 1: field-backed observer channels (registrar/dispatcher share a store). */
|
|
116
|
-
function fieldChannelEdges(queries, ctx) {
|
|
117
|
+
async function fieldChannelEdges(queries, ctx, onYield) {
|
|
117
118
|
const registrars = [];
|
|
118
119
|
const dispatchers = [];
|
|
120
|
+
let scanned = 0;
|
|
119
121
|
for (const m of methodAndFunctionNodes(queries)) {
|
|
122
|
+
if ((++scanned & 255) === 0)
|
|
123
|
+
await onYield(); // #1091: yield mid-scan on huge graphs
|
|
120
124
|
const isReg = REGISTRAR_NAME.test(m.name);
|
|
121
125
|
const isDisp = DISPATCHER_NAME.test(m.name);
|
|
122
126
|
if (!isReg && !isDisp)
|
|
@@ -197,7 +201,7 @@ function fieldChannelEdges(queries, ctx) {
|
|
|
197
201
|
* subclass `DataRequest.validate`), bounded by a fan-out cap so a generic field
|
|
198
202
|
* name shared across unrelated classes can't fan out into noise.
|
|
199
203
|
*/
|
|
200
|
-
function closureCollectionEdges(queries, ctx) {
|
|
204
|
+
async function closureCollectionEdges(queries, ctx, onYield) {
|
|
201
205
|
const dispatchers = new Map(); // field → dispatcher methods + forEach line
|
|
202
206
|
const registrars = new Map(); // field → registrar methods + append line
|
|
203
207
|
const addReg = (field, node, absLine) => {
|
|
@@ -208,7 +212,13 @@ function closureCollectionEdges(queries, ctx) {
|
|
|
208
212
|
arr.push({ node, line: absLine });
|
|
209
213
|
registrars.set(field, arr);
|
|
210
214
|
};
|
|
215
|
+
// Slices EVERY method/function's source (no cheap name-gate), so on a repo
|
|
216
|
+
// with a huge file this is the heaviest synthesis pass — yield mid-scan so it
|
|
217
|
+
// can't wedge the #850 watchdog on its own (#1091).
|
|
218
|
+
let scanned = 0;
|
|
211
219
|
for (const m of methodAndFunctionNodes(queries)) {
|
|
220
|
+
if ((++scanned & 127) === 0)
|
|
221
|
+
await onYield();
|
|
212
222
|
const content = ctx.readFile(m.filePath);
|
|
213
223
|
const src = content && sliceLines(content, m.startLine, m.endLine);
|
|
214
224
|
if (!src)
|
|
@@ -265,10 +275,13 @@ function closureCollectionEdges(queries, ctx) {
|
|
|
265
275
|
return edges;
|
|
266
276
|
}
|
|
267
277
|
/** Phase 2: string-keyed EventEmitter channels (on('e', fn) ↔ emit('e')). */
|
|
268
|
-
function eventEmitterEdges(ctx) {
|
|
278
|
+
async function eventEmitterEdges(ctx, onYield) {
|
|
269
279
|
const emitsByEvent = new Map(); // event → dispatcher node ids
|
|
270
280
|
const handlersByEvent = new Map(); // event → handler id → registration site (file:line)
|
|
281
|
+
let scanned = 0;
|
|
271
282
|
for (const file of ctx.getAllFiles()) {
|
|
283
|
+
if ((++scanned & 255) === 0)
|
|
284
|
+
await onYield(); // #1091: yield mid-scan on huge graphs
|
|
272
285
|
const content = ctx.readFile(file);
|
|
273
286
|
if (!content)
|
|
274
287
|
continue;
|
|
@@ -417,6 +430,270 @@ function flutterBuildEdges(queries, ctx) {
|
|
|
417
430
|
}
|
|
418
431
|
return edges;
|
|
419
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Reactive ArkUI property decorators: assigning a property carrying one of
|
|
435
|
+
* these re-runs the owning struct's `build()`. Covers both state models —
|
|
436
|
+
* V1 (`@Component`: State/Prop/Link/Provide/Consume/Storage*) and V2
|
|
437
|
+
* (`@ComponentV2`: Local/Provider/Consumer; `@Param` is read-only in V2 so
|
|
438
|
+
* the assignment gate never fires on it, and `@Trace` lives on `@ObservedV2`
|
|
439
|
+
* data classes, not struct properties).
|
|
440
|
+
*/
|
|
441
|
+
const ARKUI_REACTIVE_DECORATORS = new Set([
|
|
442
|
+
'State', 'Prop', 'Link', 'Provide', 'Consume', 'StorageLink', 'StorageProp',
|
|
443
|
+
'LocalStorageLink', 'LocalStorageProp', 'ObjectLink',
|
|
444
|
+
'Local', 'Provider', 'Consumer',
|
|
445
|
+
]);
|
|
446
|
+
/** ArkUI-observed array mutators — `this.todos.push(x)` re-renders like an assignment. */
|
|
447
|
+
const ARKUI_ARRAY_MUTATORS = 'push|pop|shift|unshift|splice|sort|reverse|fill';
|
|
448
|
+
/**
|
|
449
|
+
* Phase 4b-ets: ArkUI state → build (the ArkTS analog of react-render /
|
|
450
|
+
* flutter-build). Assigning a reactive-decorated property (`@State count`,
|
|
451
|
+
* `@Link selected`, …) re-runs the `@Component struct`'s `build()`, but that
|
|
452
|
+
* hop is framework-internal — no static edge — so "onClick → markAllDone →
|
|
453
|
+
* this.todos = […] → rebuilt list" dead-ends at the assignment. Bridge it:
|
|
454
|
+
* for each arkts struct with a `build()` method and at least one reactive
|
|
455
|
+
* property, link every sibling method whose body ASSIGNS (or array-mutates)
|
|
456
|
+
* one of those properties → `build`. Assignment-gated on the struct's OWN
|
|
457
|
+
* reactive property names — a method that merely reads state, or a struct
|
|
458
|
+
* with no reactive properties, gets nothing (this is the precision line the
|
|
459
|
+
* all-sibling-methods design would erase).
|
|
460
|
+
*/
|
|
461
|
+
function arkuiStateBuildEdges(queries, ctx) {
|
|
462
|
+
const edges = [];
|
|
463
|
+
const seen = new Set();
|
|
464
|
+
for (const struct of queries.getNodesByKind('struct')) {
|
|
465
|
+
if (struct.language !== 'arkts')
|
|
466
|
+
continue;
|
|
467
|
+
const children = queries.getOutgoingEdges(struct.id, ['contains'])
|
|
468
|
+
.map((e) => queries.getNodeById(e.target))
|
|
469
|
+
.filter((n) => !!n);
|
|
470
|
+
const build = children.find((n) => n.kind === 'method' && n.name === 'build');
|
|
471
|
+
if (!build)
|
|
472
|
+
continue;
|
|
473
|
+
const reactiveProps = children.filter((n) => n.kind === 'property' && (n.decorators ?? []).some((d) => ARKUI_REACTIVE_DECORATORS.has(d)));
|
|
474
|
+
if (reactiveProps.length === 0)
|
|
475
|
+
continue;
|
|
476
|
+
const propAlternation = reactiveProps
|
|
477
|
+
.map((p) => p.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
478
|
+
.join('|');
|
|
479
|
+
// `this.count = …` / `+=` / `++` / `--` / `this.todos.push(…)`. The
|
|
480
|
+
// `=(?!=)` keeps `this.done == x` comparisons out.
|
|
481
|
+
const mutationRe = new RegExp(`this\\.(?:${propAlternation})\\s*(?:=(?!=)|\\+\\+|--|[+\\-*/%&|^]=|\\.(?:${ARKUI_ARRAY_MUTATORS})\\s*\\()`);
|
|
482
|
+
let added = 0;
|
|
483
|
+
for (const m of children) {
|
|
484
|
+
if (added >= MAX_CALLBACKS_PER_CHANNEL)
|
|
485
|
+
break;
|
|
486
|
+
if (m.kind !== 'method' || m.id === build.id)
|
|
487
|
+
continue;
|
|
488
|
+
const content = ctx.readFile(m.filePath);
|
|
489
|
+
const src = content && sliceLines(content, m.startLine, m.endLine);
|
|
490
|
+
if (!src || !mutationRe.test((0, strip_comments_1.stripCommentsForRegex)(src, 'typescript')))
|
|
491
|
+
continue;
|
|
492
|
+
const key = `${m.id}>${build.id}`;
|
|
493
|
+
if (seen.has(key))
|
|
494
|
+
continue;
|
|
495
|
+
seen.add(key);
|
|
496
|
+
edges.push({
|
|
497
|
+
source: m.id, target: build.id, kind: 'calls', line: m.startLine,
|
|
498
|
+
provenance: 'heuristic',
|
|
499
|
+
metadata: { synthesizedBy: 'arkui-state', via: 'state assignment', registeredAt: `${build.filePath}:${build.startLine}` },
|
|
500
|
+
});
|
|
501
|
+
added++;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return edges;
|
|
505
|
+
}
|
|
506
|
+
/** Emit/subscribe call sites of HarmonyOS's `@ohos.events.emitter` bus. */
|
|
507
|
+
const ARKUI_EMITTER_CALL_RE = /\bemitter\s*\.\s*(emit|on|once)\s*\(\s*([A-Za-z_$][\w$.]*|\{[^)]{0,120}?\beventId\s*:\s*[^,}]+[^)]*?\})/g;
|
|
508
|
+
/** Cap per event bucket — a generic key with many parties is dynamic routing, not a static pair. */
|
|
509
|
+
const ARKUI_EMITTER_FANOUT_CAP = 8;
|
|
510
|
+
/**
|
|
511
|
+
* Phase 4b-ets2: HarmonyOS `@ohos.events.emitter` bridge. The cross-component
|
|
512
|
+
* bus — `emitter.emit(eventId)` fires `emitter.on(eventId, cb)` — is
|
|
513
|
+
* framework-internal, so an order flow riding it (OrangeShopping's
|
|
514
|
+
* add-to-cart) dead-ends at the emit. Link emit-site enclosing
|
|
515
|
+
* function/method → on/once-site enclosing function/method when both
|
|
516
|
+
* reference the SAME statically-recoverable event key.
|
|
517
|
+
*
|
|
518
|
+
* Key recovery, per call site (comment-stripped enclosing-file source): the
|
|
519
|
+
* first argument is an `{ eventId: K }` literal, a `Names.Dotted` constant, or
|
|
520
|
+
* a local whose same-file declaration is `new EventsId(K)` / `= K` — chase one
|
|
521
|
+
* level. Precision scoping learned from the samples monorepo (thousands of
|
|
522
|
+
* unrelated samples, most using eventId 1): NUMERIC keys pair within the same
|
|
523
|
+
* FILE only; NAMED keys pair within the same workspace module directory (or
|
|
524
|
+
* the whole project when it declares no modules — the single-app case), both
|
|
525
|
+
* behind a fan-out cap. Inline `on(id, (e) => {…})` arrows need no special
|
|
526
|
+
* handling — their bodies' calls already attribute to the registering method,
|
|
527
|
+
* so targeting that method keeps the chain connected.
|
|
528
|
+
*/
|
|
529
|
+
function arkuiEmitterEdges(ctx) {
|
|
530
|
+
// bucket key -> emit sites / handler sites
|
|
531
|
+
const emits = new Map();
|
|
532
|
+
const handlers = new Map();
|
|
533
|
+
const moduleDirs = (() => {
|
|
534
|
+
const ws = ctx.getWorkspacePackages?.();
|
|
535
|
+
return ws ? [...new Set(ws.byName.values())].sort((a, b) => b.length - a.length) : [];
|
|
536
|
+
})();
|
|
537
|
+
const moduleScopeOf = (file) => {
|
|
538
|
+
for (const dir of moduleDirs) {
|
|
539
|
+
if (file === dir || file.startsWith(dir + '/'))
|
|
540
|
+
return dir;
|
|
541
|
+
}
|
|
542
|
+
return '';
|
|
543
|
+
};
|
|
544
|
+
for (const file of ctx.getAllFiles()) {
|
|
545
|
+
if (!file.endsWith('.ets'))
|
|
546
|
+
continue;
|
|
547
|
+
const content = ctx.readFile(file);
|
|
548
|
+
if (!content || !content.includes('emitter.'))
|
|
549
|
+
continue;
|
|
550
|
+
const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'typescript');
|
|
551
|
+
const nodes = ctx.getNodesInFile(file)
|
|
552
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
553
|
+
ARKUI_EMITTER_CALL_RE.lastIndex = 0;
|
|
554
|
+
let m;
|
|
555
|
+
while ((m = ARKUI_EMITTER_CALL_RE.exec(safe))) {
|
|
556
|
+
const verb = m[1];
|
|
557
|
+
const arg = m[2].trim();
|
|
558
|
+
const line = safe.slice(0, m.index).split('\n').length;
|
|
559
|
+
const encl = nodes
|
|
560
|
+
.filter((n) => n.startLine <= line && n.endLine >= line)
|
|
561
|
+
.sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
|
|
562
|
+
if (!encl)
|
|
563
|
+
continue;
|
|
564
|
+
// Recover the event key from the first argument.
|
|
565
|
+
let key = null;
|
|
566
|
+
const idLit = arg.startsWith('{') ? arg.match(/\beventId\s*:\s*([\w$.]+)/)?.[1] : undefined;
|
|
567
|
+
const token = idLit ?? arg;
|
|
568
|
+
if (token !== undefined) {
|
|
569
|
+
if (/^\d+$/.test(token)) {
|
|
570
|
+
key = `num:${file}:${token}`; // numeric: same-file only
|
|
571
|
+
}
|
|
572
|
+
else if (token.includes('.')) {
|
|
573
|
+
key = `name:${moduleScopeOf(file)}:${token}`;
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
// Local variable — chase its same-file declaration one level:
|
|
577
|
+
// `let x = new EventsId(K)` / `const x = K`.
|
|
578
|
+
const declRe = new RegExp(`\\b${token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b\\s*(?::[^=\\n]+)?=\\s*(?:new\\s+[\\w$.]+\\(\\s*([^)\\n]+?)\\s*\\)|([\\w$.]+))`);
|
|
579
|
+
const decl = safe.match(declRe);
|
|
580
|
+
const inner = (decl?.[1] ?? decl?.[2])?.trim();
|
|
581
|
+
if (inner && /^\d+$/.test(inner))
|
|
582
|
+
key = `num:${file}:${inner}`;
|
|
583
|
+
else if (inner && /^[\w$.]+$/.test(inner))
|
|
584
|
+
key = `name:${moduleScopeOf(file)}:${inner}`;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (!key)
|
|
588
|
+
continue;
|
|
589
|
+
const site = { nodeId: encl.id, file, line };
|
|
590
|
+
if (verb === 'emit') {
|
|
591
|
+
(emits.get(key) ?? emits.set(key, []).get(key)).push(site);
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
(handlers.get(key) ?? handlers.set(key, []).get(key)).push(site);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const edges = [];
|
|
599
|
+
const seen = new Set();
|
|
600
|
+
for (const [key, emitSites] of emits) {
|
|
601
|
+
const handlerSites = handlers.get(key);
|
|
602
|
+
if (!handlerSites)
|
|
603
|
+
continue;
|
|
604
|
+
if (emitSites.length > ARKUI_EMITTER_FANOUT_CAP || handlerSites.length > ARKUI_EMITTER_FANOUT_CAP)
|
|
605
|
+
continue;
|
|
606
|
+
const eventLabel = key.slice(key.lastIndexOf(':') + 1);
|
|
607
|
+
for (const e of emitSites)
|
|
608
|
+
for (const h of handlerSites) {
|
|
609
|
+
if (e.nodeId === h.nodeId)
|
|
610
|
+
continue;
|
|
611
|
+
const dedupe = `${e.nodeId}>${h.nodeId}`;
|
|
612
|
+
if (seen.has(dedupe))
|
|
613
|
+
continue;
|
|
614
|
+
seen.add(dedupe);
|
|
615
|
+
edges.push({
|
|
616
|
+
source: e.nodeId, target: h.nodeId, kind: 'calls', line: e.line,
|
|
617
|
+
provenance: 'heuristic',
|
|
618
|
+
metadata: { synthesizedBy: 'arkui-emitter', event: eventLabel, registeredAt: `${h.file}:${h.line}` },
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return edges;
|
|
623
|
+
}
|
|
624
|
+
/** `router.pushUrl({ url: 'pages/Detail' })` / replaceUrl — literal urls only. */
|
|
625
|
+
const ARKUI_ROUTER_RE = /\brouter\s*\.\s*(?:pushUrl|replaceUrl)\s*\(\s*\{[^)]{0,200}?\burl\s*:\s*['"]([\w\-./]+)['"]/g;
|
|
626
|
+
/**
|
|
627
|
+
* Phase 4b-ets3: HarmonyOS page navigation. `router.pushUrl({ url:
|
|
628
|
+
* 'pages/Detail' })` reaches the `@Entry struct` of
|
|
629
|
+
* `<module>/src/main/ets/pages/Detail.ets`, but the hop is a string — no
|
|
630
|
+
* static edge — so "tap → openDetail → ???" ends at the router call. Bridge
|
|
631
|
+
* literal urls to the page struct: the url resolves against the standard
|
|
632
|
+
* `src/main/ets/` layout (what main_pages.json entries name); candidates
|
|
633
|
+
* prefer the caller's own workspace module (routes are module-scoped), and
|
|
634
|
+
* anything still ambiguous is dropped rather than guessed. Only `@Entry`
|
|
635
|
+
* structs qualify as targets — the decorator is what makes a file a page.
|
|
636
|
+
*/
|
|
637
|
+
function arkuiRouterEdges(ctx) {
|
|
638
|
+
const edges = [];
|
|
639
|
+
const seen = new Set();
|
|
640
|
+
const allFiles = ctx.getAllFiles();
|
|
641
|
+
const moduleDirs = (() => {
|
|
642
|
+
const ws = ctx.getWorkspacePackages?.();
|
|
643
|
+
return ws ? [...new Set(ws.byName.values())].sort((a, b) => b.length - a.length) : [];
|
|
644
|
+
})();
|
|
645
|
+
const moduleScopeOf = (file) => {
|
|
646
|
+
for (const dir of moduleDirs) {
|
|
647
|
+
if (file === dir || file.startsWith(dir + '/'))
|
|
648
|
+
return dir;
|
|
649
|
+
}
|
|
650
|
+
return '';
|
|
651
|
+
};
|
|
652
|
+
for (const file of allFiles) {
|
|
653
|
+
if (!file.endsWith('.ets'))
|
|
654
|
+
continue;
|
|
655
|
+
const content = ctx.readFile(file);
|
|
656
|
+
if (!content || !content.includes('router.'))
|
|
657
|
+
continue;
|
|
658
|
+
const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'typescript');
|
|
659
|
+
const nodes = ctx.getNodesInFile(file)
|
|
660
|
+
.filter((n) => n.kind === 'method' || n.kind === 'function');
|
|
661
|
+
ARKUI_ROUTER_RE.lastIndex = 0;
|
|
662
|
+
let m;
|
|
663
|
+
while ((m = ARKUI_ROUTER_RE.exec(safe))) {
|
|
664
|
+
const url = m[1];
|
|
665
|
+
const line = safe.slice(0, m.index).split('\n').length;
|
|
666
|
+
const encl = nodes
|
|
667
|
+
.filter((n) => n.startLine <= line && n.endLine >= line)
|
|
668
|
+
.sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
|
|
669
|
+
if (!encl)
|
|
670
|
+
continue;
|
|
671
|
+
const suffix = `/src/main/ets/${url}.ets`;
|
|
672
|
+
let candidates = allFiles.filter((f) => f.endsWith(suffix));
|
|
673
|
+
if (candidates.length > 1) {
|
|
674
|
+
const scope = moduleScopeOf(file);
|
|
675
|
+
const sameModule = candidates.filter((f) => moduleScopeOf(f) === scope);
|
|
676
|
+
if (sameModule.length > 0)
|
|
677
|
+
candidates = sameModule;
|
|
678
|
+
}
|
|
679
|
+
if (candidates.length !== 1)
|
|
680
|
+
continue; // ambiguous or unresolved — never guess
|
|
681
|
+
const page = ctx.getNodesInFile(candidates[0]).find((n) => n.kind === 'struct' && (n.decorators ?? []).includes('Entry'));
|
|
682
|
+
if (!page)
|
|
683
|
+
continue;
|
|
684
|
+
const key = `${encl.id}>${page.id}`;
|
|
685
|
+
if (seen.has(key))
|
|
686
|
+
continue;
|
|
687
|
+
seen.add(key);
|
|
688
|
+
edges.push({
|
|
689
|
+
source: encl.id, target: page.id, kind: 'calls', line,
|
|
690
|
+
provenance: 'heuristic',
|
|
691
|
+
metadata: { synthesizedBy: 'arkui-route', event: url, registeredAt: `${candidates[0]}:${page.startLine}` },
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return edges;
|
|
696
|
+
}
|
|
420
697
|
/**
|
|
421
698
|
* Phase 4c: C++ virtual override. A call through a base/interface pointer
|
|
422
699
|
* (`db->Get(...)`, `iter->Next()`) dispatches at runtime to a subclass override,
|
|
@@ -488,6 +765,7 @@ function cppOverrideEdges(queries) {
|
|
|
488
765
|
// or an `object` (Scala) so the loop also iterates those kinds.
|
|
489
766
|
const IFACE_OVERRIDE_LANGS = new Set([
|
|
490
767
|
'java', 'kotlin', 'csharp', 'typescript', 'javascript', 'swift', 'scala', 'go', 'rust',
|
|
768
|
+
'arkts',
|
|
491
769
|
]);
|
|
492
770
|
/**
|
|
493
771
|
* Go implicit interface satisfaction (#584). Go has no `implements` keyword — a
|
|
@@ -872,11 +1150,14 @@ function goGrpcStubImplEdges(queries) {
|
|
|
872
1150
|
* component/function/class node — TS generics like `Array<Foo>` resolve to a type
|
|
873
1151
|
* (or nothing) and are dropped.
|
|
874
1152
|
*/
|
|
875
|
-
function reactJsxChildEdges(ctx) {
|
|
1153
|
+
async function reactJsxChildEdges(ctx, onYield) {
|
|
876
1154
|
const edges = [];
|
|
877
1155
|
const seen = new Set();
|
|
878
1156
|
const PARENT_KINDS = new Set(['method', 'function', 'component']);
|
|
1157
|
+
let scanned = 0;
|
|
879
1158
|
for (const file of ctx.getAllFiles()) {
|
|
1159
|
+
if ((++scanned & 255) === 0)
|
|
1160
|
+
await onYield(); // #1091: yield mid-scan on huge graphs
|
|
880
1161
|
const content = ctx.readFile(file);
|
|
881
1162
|
if (!content || (!content.includes('</') && !content.includes('/>')))
|
|
882
1163
|
continue; // JSX-file gate
|
|
@@ -1893,10 +2174,13 @@ function resolveRegistryHandler(ctx, name, chained) {
|
|
|
1893
2174
|
// entry resolving to the global URL constant).
|
|
1894
2175
|
return cands.find((n) => n.kind === 'method') ?? null;
|
|
1895
2176
|
}
|
|
1896
|
-
function objectRegistryEdges(ctx) {
|
|
2177
|
+
async function objectRegistryEdges(ctx, onYield) {
|
|
1897
2178
|
const edges = [];
|
|
1898
2179
|
const seen = new Set();
|
|
2180
|
+
let scanned = 0;
|
|
1899
2181
|
for (const file of ctx.getAllFiles()) {
|
|
2182
|
+
if ((++scanned & 255) === 0)
|
|
2183
|
+
await onYield(); // #1091: yield mid-scan on huge graphs
|
|
1900
2184
|
if (!REGISTRY_JS_EXT.test(file))
|
|
1901
2185
|
continue;
|
|
1902
2186
|
const content = ctx.readFile(file);
|
|
@@ -2673,6 +2957,398 @@ function sidekiqDispatchEdges(ctx) {
|
|
|
2673
2957
|
}
|
|
2674
2958
|
return edges;
|
|
2675
2959
|
}
|
|
2960
|
+
// ── Erlang behaviour-callback dispatch ────────────────────────────────────────
|
|
2961
|
+
// An Erlang behaviour is a compile-checked callback contract: the behaviour
|
|
2962
|
+
// module declares `-callback init(...) -> ...`, implementers declare
|
|
2963
|
+
// `-behaviour(B)` and export the callbacks, and the framework side dispatches
|
|
2964
|
+
// through a VARIABLE module — cowboy's `Handler:init(Req, Opts)` and
|
|
2965
|
+
// `Middleware:execute(Req, Env)` folds, ejabberd's `Mod:start/2`. Extraction
|
|
2966
|
+
// deliberately leaves var-module calls silent (no static target), so the flow
|
|
2967
|
+
// breaks at exactly the hop agents ask about (request → handler init). Bridge:
|
|
2968
|
+
//
|
|
2969
|
+
// dispatch site `Var:fn(args…)` → every in-repo implementer of the behaviour
|
|
2970
|
+
// declaring `fn` with the SITE's arity — provided exactly ONE in-repo
|
|
2971
|
+
// behaviour declares (fn, arity); a name+arity collision across behaviours
|
|
2972
|
+
// bails (silent beats wrong) — and the implementer defines and exports `fn`.
|
|
2973
|
+
//
|
|
2974
|
+
// Behaviours are discovered by scanning every Erlang file for `-callback`
|
|
2975
|
+
// declarations (not just `implements` targets), so a behaviour with zero
|
|
2976
|
+
// implementers still participates in the ambiguity gate. Fan-out control: a
|
|
2977
|
+
// mega-behaviour (ejabberd's gen_mod, ~200 mod_* implementers) would mint
|
|
2978
|
+
// hundreds of edges per site that READ as complete coverage while being
|
|
2979
|
+
// arbitrary — above the cap the site is skipped entirely and the boundary
|
|
2980
|
+
// stays visibly dynamic (explore's boundary announcer covers it) instead of
|
|
2981
|
+
// silently truncated.
|
|
2982
|
+
const ERLANG_EXT = /\.(?:erl|hrl)$/;
|
|
2983
|
+
// `Var:fn(` — variable (capitalized) module, lowercase function, immediate
|
|
2984
|
+
// open-paren. The leading char class rejects `?MODULE:fn(` (macro), `a:b(`
|
|
2985
|
+
// (static remote call, already linked), and mid-word matches.
|
|
2986
|
+
const ERLANG_DISPATCH_RE = /(^|[^?\w@'])([A-Z][A-Za-z0-9_@]*):([a-z][A-Za-z0-9_@]*)\(/g;
|
|
2987
|
+
const ERLANG_CALLBACK_DECL_RE = /(^|\n)\s*-callback\s+('[^'\n]+'|[a-z][A-Za-z0-9_@]*)\s*\(/g;
|
|
2988
|
+
const ERLANG_BEHAVIOUR_FANOUT_CAP = 24;
|
|
2989
|
+
/**
|
|
2990
|
+
* Argument count of the call/declaration whose `(` sits at `openIdx` —
|
|
2991
|
+
* top-level commas + 1, `()` → 0, unbalanced/oversized → -1. Skips nested
|
|
2992
|
+
* (), [], {}, <<>> content, `"strings"`, `'atoms'`, and `$c` char literals,
|
|
2993
|
+
* so `-callback init(fun((a, b) -> ok), #{k => v}) -> ok.` counts 2.
|
|
2994
|
+
*/
|
|
2995
|
+
function erlangArityAt(src, openIdx) {
|
|
2996
|
+
let depth = 1;
|
|
2997
|
+
let commas = 0;
|
|
2998
|
+
let sawArg = false;
|
|
2999
|
+
const limit = Math.min(src.length, openIdx + 4000);
|
|
3000
|
+
for (let i = openIdx + 1; i < limit; i++) {
|
|
3001
|
+
const ch = src[i];
|
|
3002
|
+
if (ch === '"' || ch === "'") {
|
|
3003
|
+
i++;
|
|
3004
|
+
while (i < limit && src[i] !== ch) {
|
|
3005
|
+
if (src[i] === '\\')
|
|
3006
|
+
i++;
|
|
3007
|
+
i++;
|
|
3008
|
+
}
|
|
3009
|
+
sawArg = true;
|
|
3010
|
+
continue;
|
|
3011
|
+
}
|
|
3012
|
+
if (ch === '$') {
|
|
3013
|
+
i++;
|
|
3014
|
+
if (src[i] === '\\')
|
|
3015
|
+
i++;
|
|
3016
|
+
sawArg = true;
|
|
3017
|
+
continue;
|
|
3018
|
+
}
|
|
3019
|
+
if (ch === '(' || ch === '[' || ch === '{') {
|
|
3020
|
+
depth++;
|
|
3021
|
+
sawArg = true;
|
|
3022
|
+
continue;
|
|
3023
|
+
}
|
|
3024
|
+
if (ch === ')' || ch === ']' || ch === '}') {
|
|
3025
|
+
depth--;
|
|
3026
|
+
if (depth === 0)
|
|
3027
|
+
return sawArg ? commas + 1 : 0;
|
|
3028
|
+
continue;
|
|
3029
|
+
}
|
|
3030
|
+
if (ch === ',' && depth === 1) {
|
|
3031
|
+
commas++;
|
|
3032
|
+
continue;
|
|
3033
|
+
}
|
|
3034
|
+
if (!/\s/.test(ch))
|
|
3035
|
+
sawArg = true;
|
|
3036
|
+
}
|
|
3037
|
+
return -1;
|
|
3038
|
+
}
|
|
3039
|
+
/**
|
|
3040
|
+
* Nix module-system option wiring. A NixOS/home-manager/nix-darwin option is
|
|
3041
|
+
* DECLARED in one module (`options.launchd.user.agents = mkOption { ... }`)
|
|
3042
|
+
* and SET in others (`launchd.user.agents.yabai = { ... }` inside a module's
|
|
3043
|
+
* config) — the connection happens by option-path unification inside the
|
|
3044
|
+
* module-system evaluator, so there is no static call/import edge to follow
|
|
3045
|
+
* and flow questions ("how does services.yabai.enable become a launchd
|
|
3046
|
+
* service?") go dark at the module boundary.
|
|
3047
|
+
*
|
|
3048
|
+
* This pass links each config-write binding to the option declaration whose
|
|
3049
|
+
* path is the longest static-segment prefix of the write path. Precision gates:
|
|
3050
|
+
* - only STATIC segments participate: plain identifiers, plus quoted segments
|
|
3051
|
+
* (`"git/config"`, `"com.apple.dock"`) as opaque verbatim tokens that match
|
|
3052
|
+
* only quote-exactly; an interpolated (`${name}`) segment ends the prefix,
|
|
3053
|
+
* so dynamic paths never match beyond their static head;
|
|
3054
|
+
* - matched prefixes must be ≥2 segments: 1-segment paths would wrongly link
|
|
3055
|
+
* every package's `meta = { ... }` attrset to nixos's `options.meta`;
|
|
3056
|
+
* - a prefix declared in more than one file is ambiguous → no edge (a wrong
|
|
3057
|
+
* edge is worse than none);
|
|
3058
|
+
* - writes physically inside an options block are declaration internals
|
|
3059
|
+
* (types, defaults, examples), never config writes → excluded.
|
|
3060
|
+
* Both declaration spellings register: flat (`options.a.b = ...`) by name, and
|
|
3061
|
+
* nested (`options = { a.b = ...; }`) by line-span containment.
|
|
3062
|
+
*/
|
|
3063
|
+
function nixLeadingPlainSegments(name) {
|
|
3064
|
+
const segs = [];
|
|
3065
|
+
let i = 0;
|
|
3066
|
+
const n = name.length;
|
|
3067
|
+
while (i < n) {
|
|
3068
|
+
if (name[i] === '"') {
|
|
3069
|
+
// Quoted segment — an opaque verbatim token (quotes kept, so it can
|
|
3070
|
+
// never collide with a plain identifier). `NSGlobalDomain."com.apple.
|
|
3071
|
+
// mouse.tapBehavior"` must match ITS OWN quoted declaration, not
|
|
3072
|
+
// whichever sibling registered the shared plain prefix first.
|
|
3073
|
+
let j = i + 1;
|
|
3074
|
+
while (j < n && name[j] !== '"') {
|
|
3075
|
+
if (name[j] === '\\')
|
|
3076
|
+
j++;
|
|
3077
|
+
j++;
|
|
3078
|
+
}
|
|
3079
|
+
if (j >= n)
|
|
3080
|
+
return segs; // unterminated — stop at the static head
|
|
3081
|
+
const tok = name.slice(i, j + 1);
|
|
3082
|
+
if (tok.includes('${'))
|
|
3083
|
+
return segs; // interpolated → dynamic → stop
|
|
3084
|
+
segs.push(tok);
|
|
3085
|
+
i = j + 1;
|
|
3086
|
+
if (i >= n)
|
|
3087
|
+
break;
|
|
3088
|
+
if (name[i] !== '.')
|
|
3089
|
+
return segs;
|
|
3090
|
+
i++;
|
|
3091
|
+
continue;
|
|
3092
|
+
}
|
|
3093
|
+
let j = i;
|
|
3094
|
+
while (j < n && name[j] !== '.') {
|
|
3095
|
+
if (name[j] === '"' || (name[j] === '$' && name[j + 1] === '{'))
|
|
3096
|
+
return segs;
|
|
3097
|
+
j++;
|
|
3098
|
+
}
|
|
3099
|
+
const seg = name.slice(i, j);
|
|
3100
|
+
if (!/^[A-Za-z_][A-Za-z0-9_'-]*$/.test(seg))
|
|
3101
|
+
return segs;
|
|
3102
|
+
segs.push(seg);
|
|
3103
|
+
i = j + 1;
|
|
3104
|
+
}
|
|
3105
|
+
return segs;
|
|
3106
|
+
}
|
|
3107
|
+
async function nixOptionPathEdges(queries, onYield) {
|
|
3108
|
+
// One streaming pass over nix bindings (variables + the odd function-valued
|
|
3109
|
+
// option); memory stays O(bindings-kept), not O(all nodes) (#610).
|
|
3110
|
+
const byFile = new Map();
|
|
3111
|
+
let scanned = 0;
|
|
3112
|
+
for (const kind of ['variable', 'function']) {
|
|
3113
|
+
for (const node of queries.iterateNodesByKind(kind)) {
|
|
3114
|
+
if ((++scanned & 0x3fff) === 0 && onYield)
|
|
3115
|
+
await onYield();
|
|
3116
|
+
if (node.language !== 'nix')
|
|
3117
|
+
continue;
|
|
3118
|
+
const segs = nixLeadingPlainSegments(node.name);
|
|
3119
|
+
if (segs.length === 0)
|
|
3120
|
+
continue;
|
|
3121
|
+
const rec = {
|
|
3122
|
+
id: node.id,
|
|
3123
|
+
filePath: node.filePath,
|
|
3124
|
+
startLine: node.startLine,
|
|
3125
|
+
endLine: node.endLine,
|
|
3126
|
+
segs,
|
|
3127
|
+
};
|
|
3128
|
+
const arr = byFile.get(node.filePath);
|
|
3129
|
+
if (arr)
|
|
3130
|
+
arr.push(rec);
|
|
3131
|
+
else
|
|
3132
|
+
byFile.set(node.filePath, [rec]);
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
// Per file: walk bindings outermost-first with a stack of active option
|
|
3136
|
+
// spans, composing nested declaration paths (`options = { services.foo = {
|
|
3137
|
+
// enable = mkOption ...; }; }` registers services.foo AND services.foo.enable).
|
|
3138
|
+
// An `options` binding nested inside another option span is a SUBMODULE's
|
|
3139
|
+
// own namespace (`attrsOf (submodule { options = ...; })`) — its internals
|
|
3140
|
+
// are not globally addressable, so the sentinel blocks registration below it
|
|
3141
|
+
// while still excluding the region from write candidates.
|
|
3142
|
+
const SUBMODULE = 'submodule';
|
|
3143
|
+
const decls = new Map();
|
|
3144
|
+
const writes = [];
|
|
3145
|
+
const register = (path, rec) => {
|
|
3146
|
+
if (path.length < 2 || path.includes(SUBMODULE))
|
|
3147
|
+
return;
|
|
3148
|
+
const key = path.join('.');
|
|
3149
|
+
const arr = decls.get(key);
|
|
3150
|
+
if (arr)
|
|
3151
|
+
arr.push(rec);
|
|
3152
|
+
else
|
|
3153
|
+
decls.set(key, [rec]);
|
|
3154
|
+
};
|
|
3155
|
+
for (const recs of byFile.values()) {
|
|
3156
|
+
recs.sort((a, b) => a.startLine - b.startLine || b.endLine - a.endLine);
|
|
3157
|
+
const stack = [];
|
|
3158
|
+
for (const rec of recs) {
|
|
3159
|
+
while (stack.length > 0 && stack[stack.length - 1].end < rec.startLine)
|
|
3160
|
+
stack.pop();
|
|
3161
|
+
// Strict containment at line granularity: a one-line nested binding is
|
|
3162
|
+
// indistinguishable from its container, so it stays unclassified (rare
|
|
3163
|
+
// in module code, where option blocks are multi-line).
|
|
3164
|
+
const enclosing = stack.length > 0 &&
|
|
3165
|
+
rec.startLine >= stack[stack.length - 1].start &&
|
|
3166
|
+
rec.endLine <= stack[stack.length - 1].end &&
|
|
3167
|
+
!(rec.startLine === stack[stack.length - 1].start && rec.endLine === stack[stack.length - 1].end)
|
|
3168
|
+
? stack[stack.length - 1]
|
|
3169
|
+
: null;
|
|
3170
|
+
if (rec.segs[0] === 'options') {
|
|
3171
|
+
const ownPath = rec.segs.slice(1); // [] for the bare `options = { ... }` spelling
|
|
3172
|
+
const prefix = enclosing ? [SUBMODULE] : ownPath;
|
|
3173
|
+
register(prefix, rec);
|
|
3174
|
+
stack.push({ start: rec.startLine, end: rec.endLine, prefix });
|
|
3175
|
+
continue;
|
|
3176
|
+
}
|
|
3177
|
+
if (enclosing) {
|
|
3178
|
+
const composed = [...enclosing.prefix, ...rec.segs];
|
|
3179
|
+
register(composed, rec);
|
|
3180
|
+
stack.push({ start: rec.startLine, end: rec.endLine, prefix: composed });
|
|
3181
|
+
continue;
|
|
3182
|
+
}
|
|
3183
|
+
if (rec.segs.length >= 2) {
|
|
3184
|
+
writes.push(rec);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
if (decls.size === 0 || writes.length === 0)
|
|
3189
|
+
return [];
|
|
3190
|
+
const edges = [];
|
|
3191
|
+
for (const w of writes) {
|
|
3192
|
+
// `config.services.x = ...` spells the same write with an explicit prefix.
|
|
3193
|
+
const segs = w.segs[0] === 'config' ? w.segs.slice(1) : w.segs;
|
|
3194
|
+
if (segs.length < 2)
|
|
3195
|
+
continue;
|
|
3196
|
+
// Longest prefix wins; an ambiguous longest match does NOT fall back to a
|
|
3197
|
+
// shorter one (that would link `services.nginx.virtualHosts.x` to
|
|
3198
|
+
// `options.services.nginx` when virtualHosts is the contested path).
|
|
3199
|
+
for (let len = Math.min(segs.length, 6); len >= 2; len--) {
|
|
3200
|
+
const candidates = decls.get(segs.slice(0, len).join('.'));
|
|
3201
|
+
if (!candidates || candidates.length === 0)
|
|
3202
|
+
continue;
|
|
3203
|
+
const files = new Set(candidates.map((c) => c.filePath));
|
|
3204
|
+
if (files.size === 1) {
|
|
3205
|
+
const target = candidates[0];
|
|
3206
|
+
if (target.id !== w.id) {
|
|
3207
|
+
edges.push({
|
|
3208
|
+
source: w.id,
|
|
3209
|
+
target: target.id,
|
|
3210
|
+
kind: 'references',
|
|
3211
|
+
line: w.startLine,
|
|
3212
|
+
provenance: 'heuristic',
|
|
3213
|
+
metadata: {
|
|
3214
|
+
synthesizedBy: 'nix-option-path',
|
|
3215
|
+
optionPath: segs.slice(0, len).join('.'),
|
|
3216
|
+
registeredAt: `${target.filePath}:${target.startLine}`,
|
|
3217
|
+
},
|
|
3218
|
+
});
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
break; // longest hit decides, matched or ambiguous
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
return edges;
|
|
3225
|
+
}
|
|
3226
|
+
function erlangBehaviourDispatchEdges(queries, ctx) {
|
|
3227
|
+
// Cheap language gate: no Erlang modules → no cost beyond one kind query.
|
|
3228
|
+
const erlangModules = queries.getNodesByKind('namespace').filter((n) => n.language === 'erlang');
|
|
3229
|
+
if (erlangModules.length === 0)
|
|
3230
|
+
return [];
|
|
3231
|
+
// Pass 1 — scan every Erlang file with `-callback` decls: behaviour module →
|
|
3232
|
+
// its (name, arity) callback set, and the global `name/arity` → declaring
|
|
3233
|
+
// behaviours map that drives the ambiguity gate.
|
|
3234
|
+
const moduleByFile = new Map();
|
|
3235
|
+
for (const ns of erlangModules) {
|
|
3236
|
+
if (!moduleByFile.has(ns.filePath))
|
|
3237
|
+
moduleByFile.set(ns.filePath, ns);
|
|
3238
|
+
}
|
|
3239
|
+
const declaringBehaviours = new Map(); // `fn/arity` → behaviour namespaces
|
|
3240
|
+
const callbackNames = new Set();
|
|
3241
|
+
for (const file of ctx.getAllFiles()) {
|
|
3242
|
+
if (!ERLANG_EXT.test(file))
|
|
3243
|
+
continue;
|
|
3244
|
+
const behaviour = moduleByFile.get(file);
|
|
3245
|
+
if (!behaviour)
|
|
3246
|
+
continue; // a .hrl or module-less file can't be a behaviour
|
|
3247
|
+
const content = ctx.readFile(file);
|
|
3248
|
+
if (!content || !content.includes('-callback'))
|
|
3249
|
+
continue;
|
|
3250
|
+
const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'erlang');
|
|
3251
|
+
ERLANG_CALLBACK_DECL_RE.lastIndex = 0;
|
|
3252
|
+
let m;
|
|
3253
|
+
while ((m = ERLANG_CALLBACK_DECL_RE.exec(safe))) {
|
|
3254
|
+
const name = m[2].replace(/^'|'$/g, '');
|
|
3255
|
+
const arity = erlangArityAt(safe, m.index + m[0].length - 1);
|
|
3256
|
+
if (arity < 0)
|
|
3257
|
+
continue;
|
|
3258
|
+
const key = `${name}/${arity}`;
|
|
3259
|
+
const arr = declaringBehaviours.get(key);
|
|
3260
|
+
if (arr) {
|
|
3261
|
+
if (!arr.some((b) => b.id === behaviour.id))
|
|
3262
|
+
arr.push(behaviour);
|
|
3263
|
+
}
|
|
3264
|
+
else {
|
|
3265
|
+
declaringBehaviours.set(key, [behaviour]);
|
|
3266
|
+
}
|
|
3267
|
+
callbackNames.add(name);
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
if (declaringBehaviours.size === 0)
|
|
3271
|
+
return [];
|
|
3272
|
+
// Implementer target lookup, lazy per (behaviour, fn): implementers come
|
|
3273
|
+
// from the `implements` edges extraction resolved, and the target is the
|
|
3274
|
+
// implementer module's own exported `fn` function node.
|
|
3275
|
+
const targetCache = new Map();
|
|
3276
|
+
const targetsOf = (behaviour, fn) => {
|
|
3277
|
+
const cacheKey = `${behaviour.id}#${fn}`;
|
|
3278
|
+
let targets = targetCache.get(cacheKey);
|
|
3279
|
+
if (targets)
|
|
3280
|
+
return targets;
|
|
3281
|
+
targets = [];
|
|
3282
|
+
for (const e of queries.getIncomingEdges(behaviour.id, ['implements'])) {
|
|
3283
|
+
const impl = queries.getNodeById(e.source);
|
|
3284
|
+
if (!impl || impl.language !== 'erlang' || impl.kind !== 'namespace')
|
|
3285
|
+
continue;
|
|
3286
|
+
const fnNode = ctx
|
|
3287
|
+
.getNodesInFile(impl.filePath)
|
|
3288
|
+
.find((n) => n.kind === 'function' && n.name === fn && n.isExported !== false);
|
|
3289
|
+
if (fnNode)
|
|
3290
|
+
targets.push(fnNode);
|
|
3291
|
+
}
|
|
3292
|
+
targetCache.set(cacheKey, targets);
|
|
3293
|
+
return targets;
|
|
3294
|
+
};
|
|
3295
|
+
// Pass 2 — dispatch sites. Only files containing a var-module call shape are
|
|
3296
|
+
// scanned in full.
|
|
3297
|
+
const edges = [];
|
|
3298
|
+
const seen = new Set();
|
|
3299
|
+
for (const file of ctx.getAllFiles()) {
|
|
3300
|
+
if (!ERLANG_EXT.test(file))
|
|
3301
|
+
continue;
|
|
3302
|
+
const content = ctx.readFile(file);
|
|
3303
|
+
if (!content || !/[A-Z][A-Za-z0-9_@]*:[a-z]/.test(content))
|
|
3304
|
+
continue;
|
|
3305
|
+
const safe = (0, strip_comments_1.stripCommentsForRegex)(content, 'erlang');
|
|
3306
|
+
const nodesInFile = ctx.getNodesInFile(file);
|
|
3307
|
+
ERLANG_DISPATCH_RE.lastIndex = 0;
|
|
3308
|
+
let m;
|
|
3309
|
+
while ((m = ERLANG_DISPATCH_RE.exec(safe))) {
|
|
3310
|
+
const fn = m[3];
|
|
3311
|
+
if (!callbackNames.has(fn))
|
|
3312
|
+
continue;
|
|
3313
|
+
const openIdx = m.index + m[0].length - 1;
|
|
3314
|
+
const arity = erlangArityAt(safe, openIdx);
|
|
3315
|
+
if (arity < 0)
|
|
3316
|
+
continue;
|
|
3317
|
+
const behaviours = declaringBehaviours.get(`${fn}/${arity}`);
|
|
3318
|
+
if (!behaviours || behaviours.length !== 1)
|
|
3319
|
+
continue; // unknown or ambiguous
|
|
3320
|
+
const behaviour = behaviours[0];
|
|
3321
|
+
const targets = targetsOf(behaviour, fn);
|
|
3322
|
+
if (targets.length === 0 || targets.length > ERLANG_BEHAVIOUR_FANOUT_CAP)
|
|
3323
|
+
continue;
|
|
3324
|
+
const line = safe.slice(0, m.index).split('\n').length;
|
|
3325
|
+
const disp = enclosingFn(nodesInFile, line);
|
|
3326
|
+
if (!disp)
|
|
3327
|
+
continue;
|
|
3328
|
+
for (const target of targets) {
|
|
3329
|
+
if (target.id === disp.id)
|
|
3330
|
+
continue;
|
|
3331
|
+
const key = `${disp.id}>${target.id}`;
|
|
3332
|
+
if (seen.has(key))
|
|
3333
|
+
continue;
|
|
3334
|
+
seen.add(key);
|
|
3335
|
+
edges.push({
|
|
3336
|
+
source: disp.id,
|
|
3337
|
+
target: target.id,
|
|
3338
|
+
kind: 'calls',
|
|
3339
|
+
line,
|
|
3340
|
+
provenance: 'heuristic',
|
|
3341
|
+
metadata: {
|
|
3342
|
+
synthesizedBy: 'erlang-behaviour',
|
|
3343
|
+
via: `${behaviour.name}:${fn}/${arity}`,
|
|
3344
|
+
registeredAt: `${file}:${line}`,
|
|
3345
|
+
},
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
return edges;
|
|
3351
|
+
}
|
|
2676
3352
|
// ── Laravel events (PHP) ──────────────────────────────────────────────────────
|
|
2677
3353
|
// Laravel decouples an event dispatch from its listener(s), linked by the EVENT CLASS:
|
|
2678
3354
|
// // app/Events/PlaybackStarted.php + app/Listeners/UpdateLastfmNowPlaying.php
|
|
@@ -2831,7 +3507,15 @@ function laravelEventEdges(ctx) {
|
|
|
2831
3507
|
* Sidekiq Worker.perform_async → #perform + Laravel event(new X) → listener handle).
|
|
2832
3508
|
* Returns the count added. Never throws into indexing — callers wrap in try/catch.
|
|
2833
3509
|
*/
|
|
2834
|
-
function synthesizeCallbackEdges(queries, ctx) {
|
|
3510
|
+
async function synthesizeCallbackEdges(queries, ctx) {
|
|
3511
|
+
// Each sub-pass below is a whole-graph scan, and there are ~30 of them, all
|
|
3512
|
+
// running synchronously on the indexer's main thread. Their AGGREGATE can run
|
|
3513
|
+
// for well over a minute on a large repo — long enough for the #850 liveness
|
|
3514
|
+
// watchdog to SIGKILL the process mid-index (#1091), since its heartbeat lives
|
|
3515
|
+
// on this same thread. Yield between passes so the heartbeat can fire; a pass
|
|
3516
|
+
// that itself hangs (a real wedge) never reaches the next yield, so the
|
|
3517
|
+
// watchdog still catches that. See ./cooperative-yield.
|
|
3518
|
+
const yieldToLoop = (0, cooperative_yield_1.createYielder)();
|
|
2835
3519
|
// Cross-file Go method→type `contains` edges must be synthesized AND persisted
|
|
2836
3520
|
// FIRST: a method declared in a different file from its receiver type is
|
|
2837
3521
|
// otherwise orphaned from the struct, and goImplementsEdges (next) derives a
|
|
@@ -2840,6 +3524,7 @@ function synthesizeCallbackEdges(queries, ctx) {
|
|
|
2840
3524
|
const goMethodContains = goCrossFileMethodContainsEdges(queries);
|
|
2841
3525
|
if (goMethodContains.length > 0)
|
|
2842
3526
|
queries.insertEdges(goMethodContains);
|
|
3527
|
+
await yieldToLoop();
|
|
2843
3528
|
// Go implicit `implements` edges must be synthesized AND persisted next: the
|
|
2844
3529
|
// interface-dispatch bridge below reads `implements` edges from the DB, and
|
|
2845
3530
|
// Go has none statically. (Other languages already have static implements
|
|
@@ -2847,37 +3532,79 @@ function synthesizeCallbackEdges(queries, ctx) {
|
|
|
2847
3532
|
const goImpl = goImplementsEdges(queries);
|
|
2848
3533
|
if (goImpl.length > 0)
|
|
2849
3534
|
queries.insertEdges(goImpl);
|
|
2850
|
-
|
|
2851
|
-
const
|
|
2852
|
-
|
|
3535
|
+
await yieldToLoop();
|
|
3536
|
+
const fieldEdges = await fieldChannelEdges(queries, ctx, yieldToLoop);
|
|
3537
|
+
await yieldToLoop();
|
|
3538
|
+
const closureCollEdges = await closureCollectionEdges(queries, ctx, yieldToLoop);
|
|
3539
|
+
await yieldToLoop();
|
|
3540
|
+
const emitterEdges = await eventEmitterEdges(ctx, yieldToLoop);
|
|
3541
|
+
await yieldToLoop();
|
|
2853
3542
|
const renderEdges = reactRenderEdges(queries, ctx);
|
|
2854
|
-
|
|
3543
|
+
await yieldToLoop();
|
|
3544
|
+
const jsxEdges = await reactJsxChildEdges(ctx, yieldToLoop);
|
|
3545
|
+
await yieldToLoop();
|
|
2855
3546
|
const vueEdges = vueTemplateEdges(ctx);
|
|
3547
|
+
await yieldToLoop();
|
|
2856
3548
|
const svelteKitEdges = svelteKitLoadEdges(ctx);
|
|
3549
|
+
await yieldToLoop();
|
|
2857
3550
|
const pascalEdges = pascalFormEdges(ctx);
|
|
3551
|
+
await yieldToLoop();
|
|
2858
3552
|
const flutterEdges = flutterBuildEdges(queries, ctx);
|
|
3553
|
+
await yieldToLoop();
|
|
3554
|
+
const arkuiStateEdges = arkuiStateBuildEdges(queries, ctx);
|
|
3555
|
+
await yieldToLoop();
|
|
3556
|
+
const arkuiEmitter = arkuiEmitterEdges(ctx);
|
|
3557
|
+
await yieldToLoop();
|
|
3558
|
+
const arkuiRoutes = arkuiRouterEdges(ctx);
|
|
3559
|
+
await yieldToLoop();
|
|
2859
3560
|
const cppEdges = cppOverrideEdges(queries);
|
|
3561
|
+
await yieldToLoop();
|
|
2860
3562
|
const ifaceEdges = interfaceOverrideEdges(queries);
|
|
3563
|
+
await yieldToLoop();
|
|
2861
3564
|
const kotlinExpectActual = kotlinExpectActualEdges(queries);
|
|
3565
|
+
await yieldToLoop();
|
|
2862
3566
|
const goGrpcEdges = goGrpcStubImplEdges(queries);
|
|
3567
|
+
await yieldToLoop();
|
|
2863
3568
|
const rnEventEdgesList = rnEventEdges(ctx);
|
|
3569
|
+
await yieldToLoop();
|
|
2864
3570
|
const fabricNativeEdges = fabricNativeImplEdges(ctx);
|
|
3571
|
+
await yieldToLoop();
|
|
2865
3572
|
const expoXPlatEdges = expoCrossPlatformEdges(queries);
|
|
3573
|
+
await yieldToLoop();
|
|
2866
3574
|
const rnXPlatEdges = rnCrossPlatformEdges(queries);
|
|
3575
|
+
await yieldToLoop();
|
|
2867
3576
|
const mybatisEdges = mybatisJavaXmlEdges(queries);
|
|
3577
|
+
await yieldToLoop();
|
|
2868
3578
|
const ginEdges = ginMiddlewareChainEdges(queries, ctx);
|
|
3579
|
+
await yieldToLoop();
|
|
2869
3580
|
const thunkEdges = reduxThunkEdges(queries, ctx);
|
|
2870
|
-
|
|
3581
|
+
await yieldToLoop();
|
|
3582
|
+
const registryEdges = await objectRegistryEdges(ctx, yieldToLoop);
|
|
3583
|
+
await yieldToLoop();
|
|
2871
3584
|
const rtkEdges = rtkQueryEdges(queries, ctx);
|
|
3585
|
+
await yieldToLoop();
|
|
2872
3586
|
const piniaEdges = piniaStoreEdges(ctx);
|
|
3587
|
+
await yieldToLoop();
|
|
2873
3588
|
const vuexEdges = vuexDispatchEdges(ctx);
|
|
3589
|
+
await yieldToLoop();
|
|
2874
3590
|
const celeryEdges = celeryDispatchEdges(ctx);
|
|
3591
|
+
await yieldToLoop();
|
|
2875
3592
|
const springEdges = springEventEdges(ctx);
|
|
3593
|
+
await yieldToLoop();
|
|
2876
3594
|
const mediatrEdges = mediatrDispatchEdges(ctx);
|
|
3595
|
+
await yieldToLoop();
|
|
2877
3596
|
const sidekiqEdges = sidekiqDispatchEdges(ctx);
|
|
3597
|
+
await yieldToLoop();
|
|
3598
|
+
const erlangBehaviourEdges = erlangBehaviourDispatchEdges(queries, ctx);
|
|
3599
|
+
await yieldToLoop();
|
|
2878
3600
|
const laravelEdges = laravelEventEdges(ctx);
|
|
3601
|
+
await yieldToLoop();
|
|
2879
3602
|
const cFnPtrEdges = (0, c_fnptr_synthesizer_1.cFnPointerDispatchEdges)(queries, ctx);
|
|
3603
|
+
await yieldToLoop();
|
|
2880
3604
|
const goframeEdges = (0, goframe_synthesizer_1.goframeRouteEdges)(ctx);
|
|
3605
|
+
await yieldToLoop();
|
|
3606
|
+
const nixOptionEdges = await nixOptionPathEdges(queries, yieldToLoop);
|
|
3607
|
+
await yieldToLoop();
|
|
2881
3608
|
const merged = [];
|
|
2882
3609
|
const seen = new Set();
|
|
2883
3610
|
for (const e of [
|
|
@@ -2890,6 +3617,9 @@ function synthesizeCallbackEdges(queries, ctx) {
|
|
|
2890
3617
|
...svelteKitEdges,
|
|
2891
3618
|
...pascalEdges,
|
|
2892
3619
|
...flutterEdges,
|
|
3620
|
+
...arkuiStateEdges,
|
|
3621
|
+
...arkuiEmitter,
|
|
3622
|
+
...arkuiRoutes,
|
|
2893
3623
|
...cppEdges,
|
|
2894
3624
|
...ifaceEdges,
|
|
2895
3625
|
...kotlinExpectActual,
|
|
@@ -2909,9 +3639,11 @@ function synthesizeCallbackEdges(queries, ctx) {
|
|
|
2909
3639
|
...springEdges,
|
|
2910
3640
|
...mediatrEdges,
|
|
2911
3641
|
...sidekiqEdges,
|
|
3642
|
+
...erlangBehaviourEdges,
|
|
2912
3643
|
...laravelEdges,
|
|
2913
3644
|
...cFnPtrEdges,
|
|
2914
3645
|
...goframeEdges,
|
|
3646
|
+
...nixOptionEdges,
|
|
2915
3647
|
]) {
|
|
2916
3648
|
const key = `${e.source}>${e.target}`;
|
|
2917
3649
|
if (seen.has(key))
|