Commit Graph
3 Commits
Author SHA1 Message Date
Colby McHenryandClaude Opus 4.8 d06a5ec890 feat(impact): extract RCT_EXPORT_METHOD / RCT_REMAP_METHOD as ObjC method nodes
`RCT_EXPORT_METHOD(foo:(...))` — the most common way to declare an iOS native
module method — parses as a macro-expression (an ERROR node), NOT a
`method_definition`, so the ObjC extractor never made a node for it. The iOS half
of a native module was invisible: a JS call to it couldn't resolve, and the
cross-platform pairing had nothing to pair. Only regular `- (void)foo` ObjC
methods were nodes.

The React Native bridge resolver now implements the `extract()` framework hook
for `.m`/`.mm` files: it reuses the existing `RCT_EXPORT_MODULE` /
`RCT_EXPORT_METHOD` / `RCT_REMAP_METHOD` source parser to emit a method node per
exported method, named by its JS-visible name (the selector's first keyword, or
the explicit `RCT_REMAP_METHOD` JS name) so it lines up with the Android
`@ReactMethod` method and the JS callsite. Node id is prefixed `rn-export:`.

Measured on react-native-device-info: JS→ObjC `calls` went 7 → 37, and Java↔ObjC
cross-platform pairs 22 → 29 (ObjC↔C++ 8 → 24) — the iOS bridge surface is now
visible and connected. Full suite green; node count grows only by the newly-
visible iOS methods.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:06:33 -04:00
Colby McHenryandClaude Opus 4.8 4a64ca56cd feat(impact): classic RN NativeModules cross-platform pairing (Android @ReactMethod ↔ iOS impl)
The classic React Native bridge links a JS callsite to a native module method,
but only ONE platform's impl — the other (the iOS method when JS resolved to
Android, or vice versa) looked like nothing called it and editing it showed no
blast radius. Same gap fixed for Expo, now for the classic bridge.

`rnCrossPlatformEdges`: a native method (java/kotlin/objc/cpp) that HAS a JS-side
`calls` edge is a confirmed bridge method; link it to the same-named native
method in another language (the other platform's impl), both directions. Names
are normalized to the first selector keyword (`getFreeDiskStorage:` →
`getFreeDiskStorage`) so an iOS selector lines up with the bare Android name.
RN runtime-infrastructure methods (addListener / getConstants / getName / … —
present on every module) are excluded so they don't cross-link unrelated modules
in a multi-module repo.

Measured on react-native-device-info: 152 cross-platform pairs (Java↔ObjC↔C++);
the classic JS→Java/ObjC bridge itself is unchanged. Heuristic edges (provenance
`heuristic`, `synthesizedBy:'rn-cross-platform'`). Full suite green; node count
stable. (Note: iOS methods written with the `RCT_EXPORT_METHOD` macro parse as a
macro, not a method node, so only regular ObjC method impls pair today — macro
extraction is a separate follow-up.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:55:46 -04:00
Colby Mchenry 4d1a2b3c4d feat(resolution): mixed iOS / React Native / Expo cross-language bridging (#430)
Implements the design from `docs/design/mixed-ios-and-react-native-bridging.md`.
Closes the cross-language flow gap so `trace` / `callers` / `callees` / `impact` connect end-to-end across language boundaries in real iOS, React Native, and Expo codebases.

## Bridges shipped

| Boundary | Mechanism | Real-codebase validation |
|---|---|---|
| **Swift ↔ Objective-C** | Resolver applying Apple's @objc auto-bridging name math + Cocoa preposition prefixes | Charts (S, 269) · realm-swift (M, 369) · wikipedia-ios (L, 1734) |
| **React Native legacy bridge** | Resolver parsing `RCT_EXPORT_MODULE` / `RCT_EXPORT_METHOD` / `RCT_REMAP_METHOD` (ObjC) + `@ReactMethod` (Java/Kotlin) | AsyncStorage (S, ~60) · react-native-svg (M, ~700) · react-native-firebase (L, ~1100) |
| **React Native TurboModules** | Resolver treating `Native<X>.ts` spec interface as ground truth | via RNSvg + RNFirebase subsets |
| **Native → JS events** | Synthesizer matching native `sendEventWithName:`/`emit(...)` to JS `addListener('e', handler)` keyed by literal event name; falls back to enclosing constant/variable for wrapper-API parameter handlers | RNGeolocation (S) · RNFirebase (L) |
| **Expo Modules** | Framework extract synthesizes `method` nodes from Swift/Kotlin `Module { Name("X"); Function("y") { ... } }` DSL | expo-haptics (S, 14) · expo-camera (M, 72) · ExpoSweep (L, 332, 7 packages) |
| **Fabric + legacy Paper view components** | Extract `component` + `property` nodes from Codegen `codegenNativeComponent<Props>('Name', ...)` specs AND legacy `RCT_EXPORT_VIEW_PROPERTY` / `@ReactProp` macros, then synthesize component → native class by name+suffix convention | react-native-segmented-control (S, legacy) · react-native-screens (M, Codegen) · react-native-skia (L, hybrid monorepo) |

## Bug fixes surfaced along the way

- `tree-sitter.ts` message_expression — multi-keyword ObjC call sites now reconstruct `a🅱️` selectors so they resolve to multi-part method definitions (gap discovered post-#165; 0 → 84 call edges to `GET:parameters:...` style methods on AFNetworking).
- `src/index.ts` resolver lifecycle — `indexAll()` now re-initializes the resolver after extraction so framework `detect()` sees the populated index. Pre-existing latent bug that affected UIKit and SwiftUI resolvers too.
- `src/extraction/index.ts` `buildDetectionContext` — added `listDirectories` so framework detect() can probe monorepo subpackages uniformly (fix needed for react-native-skia detection).

## Regression check on 5 control repos

| Repo | Result |
|---|---|
| Express (small JS) | ✅ unchanged — 266 routes, express framework detected |
| Excalidraw (medium TS/React) | ✅ 9284 nodes (CLAUDE.md baseline ~9290); canonical `trace(mutateElement, renderStaticScene)` returns the flow |
| Django realworld (Python) | ✅ django framework detected, 16 routes |
| Spring petclinic (Java) | ✅ spring framework detected, 17 routes |
| Texture (pure ObjC, large) | ✅ exactly matches #165 baseline: 4702 methods, 894 classes, 808/808 file coverage, 913 multi-keyword selectors, 55 protocols, 1036 properties |

## Tests

928 passing (+87 net new bridge tests across the 5 channels); 2 pre-existing skips. The mcp-staleness-banner / watcher parallel flakiness is unchanged by this work (different test fails each run, all pass in isolation; pre-existing on main).

## Documentation

- README: new 'Mixed iOS / React Native / Expo bridging' section with the per-boundary table and validation-corpus links.
- CHANGELOG `[Unreleased]`: full entry per bridge with measurements.
- `docs/design/mixed-ios-and-react-native-bridging.md`: the design doc (§8 measurements filled in across §8a-§8g).
- `docs/design/dynamic-dispatch-coverage-playbook.md` §6 coverage matrix: six new rows.
- `.claude/skills/agent-eval/corpus.json`: four new sections covering 15 real GitHub repos for the eval harness.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-26 02:14:00 -05:00