MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / celeryDispatchEdges

Function celeryDispatchEdges

src/resolution/callback-synthesizer.ts:2564–2635  ·  view source on GitHub ↗
(ctx: ResolutionContext, onYield: MaybeYield)

Source from the content-addressed store, hash-verified

2562const CELERY_DECORATOR_LOOKBACK = 12; // max lines above a `def` to scan for its decorators
2563
2564async function celeryDispatchEdges(ctx: ResolutionContext, onYield: MaybeYield): Promise<Edge[]> {
2565 let scannedFiles = 0;
2566 // Memoize the decorator check per task-candidate node: it reads the file and scans a few
2567 // lines above the def. Only called on names that are actually `.delay`/`.apply_async`
2568 // receivers, so the candidate set stays small.
2569 const taskCache = new Map<string, boolean>();
2570 const isCeleryTask = (node: Node): boolean => {
2571 let v = taskCache.get(node.id);
2572 if (v !== undefined) return v;
2573 v = false;
2574 if (node.kind === 'function' && CELERY_PY_EXT.test(node.filePath)) {
2575 const content = ctx.readFile(node.filePath);
2576 if (content) {
2577 const lines = content.split('\n');
2578 // startLine is the `def` line (decorators sit ABOVE it). Walk upward, stopping at the
2579 // previous declaration so a non-task def can never inherit the prior def's decorator.
2580 const stop = Math.max(0, node.startLine - 1 - CELERY_DECORATOR_LOOKBACK);
2581 for (let i = node.startLine - 2; i >= stop; i--) {
2582 const t = (lines[i] ?? '').trim();
2583 if (/^(?:async\s+def|def|class)\b/.test(t)) break; // previous decl → stop
2584 if (CELERY_TASK_DECORATOR_RE.test(t)) { v = true; break; }
2585 }
2586 }
2587 }
2588 taskCache.set(node.id, v);
2589 return v;
2590 };
2591
2592 const resolve = (name: string, dispatchFile: string): Node | null => {
2593 const cands = ctx.getNodesByName(name).filter((n) => n.kind === 'function' && isCeleryTask(n));
2594 if (!cands.length) return null;
2595 if (cands.length === 1) return cands[0]!;
2596 // Cross-module name collision: prefer a task defined in the dispatching file, else bail
2597 // (ambiguous — precision over recall, like vuex's root-key resolution).
2598 return cands.find((c) => c.filePath === dispatchFile) ?? null;
2599 };
2600
2601 const edges: Edge[] = [];
2602 const seen = new Set<string>();
2603 for (const file of ctx.getAllFiles()) {
2604 if ((++scannedFiles & 15) === 0) await onYield();
2605 if (!CELERY_PY_EXT.test(file)) continue;
2606 const content = ctx.readFile(file);
2607 if (!content || (!content.includes('.delay(') && !content.includes('.apply_async('))) continue;
2608 const safe = stripCommentsForRegex(content, 'python');
2609 const nodesInFile = ctx.getNodesInFile(file);
2610 CELERY_DISPATCH_RE.lastIndex = 0;
2611 let m: RegExpExecArray | null;
2612 let added = 0;
2613 while ((m = CELERY_DISPATCH_RE.exec(safe)) && added < CELERY_FANOUT_CAP) {
2614 const name = m[1]!;
2615 const line = safe.slice(0, m.index).split('\n').length;
2616 const disp = enclosingFn(nodesInFile, line);
2617 if (!disp) continue; // module-level dispatch — no source symbol to attribute
2618 const target = resolve(name, file);
2619 if (!target || target.id === disp.id) continue;
2620 const key = `${disp.id}>${target.id}`;
2621 if (seen.has(key)) continue;

Callers 1

Calls 8

stripCommentsForRegexFunction · 0.90
enclosingFnFunction · 0.85
readFileMethod · 0.80
hasMethod · 0.80
resolveFunction · 0.70
getAllFilesMethod · 0.65
getNodesInFileMethod · 0.65
execMethod · 0.65

Tested by

no test coverage detected