mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-31 08:03:58 +08:00
feat(impact): RN native→JS events via the sendEvent(ctx, "X", body) wrapper
Many React Native libraries (react-native-device-info and others) emit events
through a custom `sendEvent(reactContext, "eventName", body)` helper that
internally calls `…emit(eventName, …)` with the event name as a VARIABLE — so
RN_JVM_EMIT_RE (which matches `.emit("literal", …)`) saw only the variable and
produced no event edge. The literal event name lives in the wrapper CALL.
Add RN_NATIVE_SENDEVENT_RE — captures the first string literal inside a
`sendEvent(...)` call (multi-line tolerant; `[^;{}]*?` keeps it to one statement
so the wrapper DEFINITION, whose `(` is followed by `… ) {`, never matches).
Wired for Java/Kotlin and Swift. A native method firing `sendEvent(…, "X", …)`
now links to the JS `addListener('X', handler)`.
Measured on react-native-device-info: the Java-side battery/power/headphone
events now connect to their JS hooks (usePowerState, useBatteryLevel, …) —
previously only the ObjC `sendEventWithName:` side did. Full suite green.
(Expo's constant-based events — `sendEvent(batteryLevelDidChange, …)` where the
name is a `const` — still need constant resolution; tracked as a follow-up.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d06a5ec890
commit
74b599c86b
@@ -906,6 +906,14 @@ const RN_SWIFT_SEND_RE = /\bsendEvent\s*\(\s*withName\s*:\s*"([^"]+)"/g;
|
||||
// JVM source files in the consumer so we don't re-process JS emits
|
||||
// (which `eventEmitterEdges` already handles).
|
||||
const RN_JVM_EMIT_RE = /\.emit\s*\(\s*"([^"]+)"\s*,/g;
|
||||
// Custom `sendEvent(reactContext, "X", body)` wrapper — extremely common
|
||||
// (react-native-device-info and many libs wrap `DeviceEventManagerModule…emit`
|
||||
// behind a helper whose `.emit(eventName, …)` uses a VARIABLE, so RN_JVM_EMIT_RE
|
||||
// misses it; the literal lives in the wrapper CALL instead). Captures the first
|
||||
// string literal inside a `sendEvent(...)` call. `[^;{}]*?` keeps it on one
|
||||
// statement and stops at a block boundary, so the wrapper DEFINITION (whose `(`
|
||||
// is followed by `… ) {`) never matches. Multi-line tolerant. (java/kotlin/swift)
|
||||
const RN_NATIVE_SENDEVENT_RE = /\bsendEvent\s*\([^;{}]*?"([^"]+)"/g;
|
||||
|
||||
function rnEventEdges(ctx: ResolutionContext): Edge[] {
|
||||
// Native dispatchers (source = the native method whose body sends the
|
||||
@@ -945,17 +953,26 @@ function rnEventEdges(ctx: ResolutionContext): Edge[] {
|
||||
while ((m = RN_SWIFT_SEND_RE.exec(content))) {
|
||||
if (m[1]) addDispatcher(m[1], lineOf(m.index));
|
||||
}
|
||||
RN_NATIVE_SENDEVENT_RE.lastIndex = 0;
|
||||
while ((m = RN_NATIVE_SENDEVENT_RE.exec(content))) {
|
||||
if (m[1]) addDispatcher(m[1], lineOf(m.index));
|
||||
}
|
||||
}
|
||||
|
||||
// JVM side: `.emit("X", …)` in Java/Kotlin. (We pattern-match
|
||||
// anywhere in the file; the JS in-language path uses a separate
|
||||
// emitter object pattern and is already handled by eventEmitterEdges.)
|
||||
// JVM side: `.emit("X", …)` in Java/Kotlin, plus the common
|
||||
// `sendEvent(ctx, "X", body)` wrapper. (We pattern-match anywhere in the
|
||||
// file; the JS in-language path uses a separate emitter object pattern and
|
||||
// is already handled by eventEmitterEdges.)
|
||||
if (file.endsWith('.java') || file.endsWith('.kt')) {
|
||||
RN_JVM_EMIT_RE.lastIndex = 0;
|
||||
let m: RegExpExecArray | null;
|
||||
RN_JVM_EMIT_RE.lastIndex = 0;
|
||||
while ((m = RN_JVM_EMIT_RE.exec(content))) {
|
||||
if (m[1]) addDispatcher(m[1], lineOf(m.index));
|
||||
}
|
||||
RN_NATIVE_SENDEVENT_RE.lastIndex = 0;
|
||||
while ((m = RN_NATIVE_SENDEVENT_RE.exec(content))) {
|
||||
if (m[1]) addDispatcher(m[1], lineOf(m.index));
|
||||
}
|
||||
}
|
||||
|
||||
// JS subscribers (.addListener("X", handler)). Restrict to JS-family
|
||||
|
||||
Reference in New Issue
Block a user