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

Function sidekiqDispatchEdges

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

Source from the content-addressed store, hash-verified

2894const SIDEKIQ_FANOUT_CAP = 80;
2895
2896async function sidekiqDispatchEdges(ctx: ResolutionContext, onYield: MaybeYield): Promise<Edge[]> {
2897 let scannedFiles = 0;
2898 // class node id → its instance `perform` method (null if the class isn't a Sidekiq worker),
2899 // memoized. Reads the class body for the mixin; only consulted for actual dispatch receivers.
2900 const performCache = new Map<string, Node | null>();
2901 const performOf = (cls: Node): Node | null => {
2902 let v = performCache.get(cls.id);
2903 if (v !== undefined) return v;
2904 v = null;
2905 const content = ctx.readFile(cls.filePath);
2906 if (content) {
2907 const end = cls.endLine ?? cls.startLine;
2908 const body = content.split('\n').slice(cls.startLine - 1, end).join('\n');
2909 if (SIDEKIQ_WORKER_RE.test(body)) {
2910 v = ctx.getNodesInFile(cls.filePath).find(
2911 (n) => n.kind === 'method' && n.name === 'perform' && n.startLine >= cls.startLine && n.startLine <= end
2912 ) ?? null;
2913 }
2914 }
2915 performCache.set(cls.id, v);
2916 return v;
2917 };
2918
2919 // Resolve a (possibly namespaced) worker reference to its `perform`. A namespaced ref is
2920 // matched by EXACT qualified name first, so same-named workers in different namespaces
2921 // (forem has four `SendEmailNotificationWorker`s) resolve to the right one; an unqualified
2922 // ref falls back to the simple name and links only when a single worker bears it — an
2923 // ambiguous collision bails (precision over recall).
2924 const resolve = (ref: string): Node | null => {
2925 if (ref.includes('::')) {
2926 const q = ctx.getNodesByQualifiedName(ref).find((n) => n.kind === 'class' && performOf(n));
2927 if (q) return performOf(q);
2928 }
2929 const workers = ctx.getNodesByName(ref.split('::').pop()!).filter((n) => n.kind === 'class' && performOf(n));
2930 return workers.length === 1 ? performOf(workers[0]!) : null;
2931 };
2932
2933 const edges: Edge[] = [];
2934 const seen = new Set<string>();
2935 for (const file of ctx.getAllFiles()) {
2936 if ((++scannedFiles & 15) === 0) await onYield();
2937 if (!SIDEKIQ_RB_EXT.test(file)) continue;
2938 const content = ctx.readFile(file);
2939 if (!content || !/\.perform_(?:async|in|at)\b/.test(content)) continue;
2940 const safe = stripCommentsForRegex(content, 'ruby');
2941 const nodesInFile = ctx.getNodesInFile(file);
2942 SIDEKIQ_DISPATCH_RE.lastIndex = 0;
2943 let m: RegExpExecArray | null;
2944 let added = 0;
2945 while ((m = SIDEKIQ_DISPATCH_RE.exec(safe)) && added < SIDEKIQ_FANOUT_CAP) {
2946 const line = safe.slice(0, m.index).split('\n').length;
2947 const disp = enclosingFn(nodesInFile, line);
2948 if (!disp) continue;
2949 const target = resolve(m[1]!);
2950 if (!target || target.id === disp.id) continue;
2951 const key = `${disp.id}>${target.id}`;
2952 if (seen.has(key)) continue;
2953 seen.add(key);

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