(ctx: ResolutionContext, onYield: MaybeYield)
| 2381 | const PINIA_FANOUT_CAP = 80; |
| 2382 | |
| 2383 | async function piniaStoreEdges(ctx: ResolutionContext, onYield: MaybeYield): Promise<Edge[]> { |
| 2384 | let scannedFiles = 0; |
| 2385 | // 1. Map each `const useXStore = defineStore(...)` factory → its store file. |
| 2386 | const factoryFile = new Map<string, string>(); |
| 2387 | for (const file of ctx.getAllFiles()) { |
| 2388 | if ((++scannedFiles & 15) === 0) await onYield(); |
| 2389 | if (!PINIA_CONSUMER_EXT.test(file)) continue; |
| 2390 | const content = ctx.readFile(file); |
| 2391 | if (!content || !content.includes('defineStore')) continue; |
| 2392 | PINIA_FACTORY_RE.lastIndex = 0; |
| 2393 | let m: RegExpExecArray | null; |
| 2394 | while ((m = PINIA_FACTORY_RE.exec(content))) factoryFile.set(m[1]!, file); |
| 2395 | } |
| 2396 | if (!factoryFile.size) return []; |
| 2397 | |
| 2398 | const edges: Edge[] = []; |
| 2399 | const seen = new Set<string>(); |
| 2400 | for (const file of ctx.getAllFiles()) { |
| 2401 | if ((++scannedFiles & 15) === 0) await onYield(); |
| 2402 | if (!PINIA_CONSUMER_EXT.test(file)) continue; |
| 2403 | const content = ctx.readFile(file); |
| 2404 | if (!content || !content.includes('Store')) continue; |
| 2405 | const safe = stripCommentsForRegex(content, /\.(?:jsx?|mjs|cjs)$/.test(file) ? 'javascript' : 'typescript'); |
| 2406 | |
| 2407 | // 2. Bind store vars in this file: `const <var> = <known-factory>(...)`. |
| 2408 | const varStore = new Map<string, string>(); |
| 2409 | PINIA_BIND_RE.lastIndex = 0; |
| 2410 | let bm: RegExpExecArray | null; |
| 2411 | while ((bm = PINIA_BIND_RE.exec(safe))) { |
| 2412 | const sf = factoryFile.get(bm[2]!); |
| 2413 | if (sf) varStore.set(bm[1]!, sf); |
| 2414 | } |
| 2415 | if (!varStore.size) continue; |
| 2416 | |
| 2417 | // 3. Link `<var>.<method>(` → the action function node in the store's file. |
| 2418 | const nodesInFile = ctx.getNodesInFile(file); |
| 2419 | const fallbackDispatcher = nodesInFile.find((n) => n.kind === 'component'); // .vue top-level setup |
| 2420 | PINIA_CALL_RE.lastIndex = 0; |
| 2421 | let cm: RegExpExecArray | null; |
| 2422 | let added = 0; |
| 2423 | while ((cm = PINIA_CALL_RE.exec(safe)) && added < PINIA_FANOUT_CAP) { |
| 2424 | const storeFile = varStore.get(cm[1]!); |
| 2425 | if (!storeFile) continue; |
| 2426 | const method = cm[2]!; |
| 2427 | const line = safe.slice(0, cm.index).split('\n').length; |
| 2428 | const disp = enclosingFn(nodesInFile, line) ?? fallbackDispatcher; |
| 2429 | if (!disp) continue; |
| 2430 | const target = ctx |
| 2431 | .getNodesByName(method) |
| 2432 | .find((n) => n.kind === 'function' && n.filePath === storeFile); |
| 2433 | if (!target || target.id === disp.id) continue; |
| 2434 | const key = `${disp.id}>${target.id}`; |
| 2435 | if (seen.has(key)) continue; |
| 2436 | seen.add(key); |
| 2437 | edges.push({ |
| 2438 | source: disp.id, |
| 2439 | target: target.id, |
| 2440 | kind: 'calls', |
no test coverage detected