(ctx: ResolutionContext, onYield: MaybeYield)
| 1431 | const RN_NATIVE_SENDEVENT_RE = /\bsendEvent\s*\([^;{}]*?"([^"]+)"/g; |
| 1432 | |
| 1433 | async function rnEventEdges(ctx: ResolutionContext, onYield: MaybeYield): Promise<Edge[]> { |
| 1434 | let scannedFiles = 0; |
| 1435 | // Native dispatchers (source = the native method whose body sends the |
| 1436 | // event) and JS handlers (target = the function/method registered as |
| 1437 | // the listener) keyed by event name. |
| 1438 | const nativeDispatchersByEvent = new Map<string, Set<string>>(); |
| 1439 | const jsHandlersByEvent = new Map<string, Map<string, string>>(); |
| 1440 | |
| 1441 | for (const file of ctx.getAllFiles()) { |
| 1442 | if ((++scannedFiles & 15) === 0) await onYield(); |
| 1443 | const content = ctx.readFile(file); |
| 1444 | if (!content) continue; |
| 1445 | |
| 1446 | const nodesInFile = ctx.getNodesInFile(file); |
| 1447 | const lineOf = makeLineAt(content, 1); |
| 1448 | const addDispatcher = (event: string, line: number) => { |
| 1449 | const disp = enclosingFn(nodesInFile, line); |
| 1450 | if (!disp) return; |
| 1451 | const set = nativeDispatchersByEvent.get(event) ?? new Set<string>(); |
| 1452 | set.add(disp.id); |
| 1453 | nativeDispatchersByEvent.set(event, set); |
| 1454 | }; |
| 1455 | |
| 1456 | // ObjC side: `sendEventWithName:@"X"` only fires inside `.m`/`.mm` |
| 1457 | // files (RCTEventEmitter subclasses). |
| 1458 | if (file.endsWith('.m') || file.endsWith('.mm')) { |
| 1459 | RN_OBJC_SEND_RE.lastIndex = 0; |
| 1460 | let m: RegExpExecArray | null; |
| 1461 | while ((m = RN_OBJC_SEND_RE.exec(content))) { |
| 1462 | if (m[1]) addDispatcher(m[1], lineOf(m.index)); |
| 1463 | } |
| 1464 | } |
| 1465 | |
| 1466 | // Swift side: same RCTEventEmitter method, parens/named-args syntax. |
| 1467 | if (file.endsWith('.swift')) { |
| 1468 | RN_SWIFT_SEND_RE.lastIndex = 0; |
| 1469 | let m: RegExpExecArray | null; |
| 1470 | while ((m = RN_SWIFT_SEND_RE.exec(content))) { |
| 1471 | if (m[1]) addDispatcher(m[1], lineOf(m.index)); |
| 1472 | } |
| 1473 | RN_NATIVE_SENDEVENT_RE.lastIndex = 0; |
| 1474 | while ((m = RN_NATIVE_SENDEVENT_RE.exec(content))) { |
| 1475 | if (m[1]) addDispatcher(m[1], lineOf(m.index)); |
| 1476 | } |
| 1477 | } |
| 1478 | |
| 1479 | // JVM side: `.emit("X", …)` in Java/Kotlin, plus the common |
| 1480 | // `sendEvent(ctx, "X", body)` wrapper. (We pattern-match anywhere in the |
| 1481 | // file; the JS in-language path uses a separate emitter object pattern and |
| 1482 | // is already handled by eventEmitterEdges.) |
| 1483 | if (file.endsWith('.java') || file.endsWith('.kt')) { |
| 1484 | let m: RegExpExecArray | null; |
| 1485 | RN_JVM_EMIT_RE.lastIndex = 0; |
| 1486 | while ((m = RN_JVM_EMIT_RE.exec(content))) { |
| 1487 | if (m[1]) addDispatcher(m[1], lineOf(m.index)); |
| 1488 | } |
| 1489 | RN_NATIVE_SENDEVENT_RE.lastIndex = 0; |
| 1490 | while ((m = RN_NATIVE_SENDEVENT_RE.exec(content))) { |
no test coverage detected