* Parse an ObjC `.m`/`.mm` file's source for `RCT_EXPORT_MODULE` and * `RCT_EXPORT_METHOD` / `RCT_REMAP_METHOD` declarations, returning the * inferred (moduleName, jsMethodName) pairs. * * The macro forms (a single `RCT_EXPORT_MODULE` per file conventionally * matched to a single `@implementati
( source: string, className: string | null )
| 97 | * macro-aware ObjC parse the tree-sitter grammar doesn't provide. |
| 98 | */ |
| 99 | function parseObjcRNExports( |
| 100 | source: string, |
| 101 | className: string | null |
| 102 | ): Array<{ moduleName: string; jsName: string; nativeSelectorFirstKw: string; line: number }> { |
| 103 | const results: Array<{ moduleName: string; jsName: string; nativeSelectorFirstKw: string; line: number }> = []; |
| 104 | |
| 105 | // RCT_EXPORT_MODULE — one per file by convention. Capture the optional arg. |
| 106 | const moduleMatch = source.match(/RCT_EXPORT_MODULE\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)?\s*\)/); |
| 107 | // Need a module name to attribute methods. Prefer the explicit macro arg, |
| 108 | // then the class name, then bail (no module = nothing useful to register). |
| 109 | const moduleName = |
| 110 | moduleMatch?.[1] ?? |
| 111 | (className ? defaultObjcModuleName(className) : null); |
| 112 | if (!moduleName) return results; |
| 113 | |
| 114 | const lineOf = (idx: number): number => { |
| 115 | let line = 1; |
| 116 | for (let i = 0; i < idx && i < source.length; i++) if (source.charCodeAt(i) === 10) line++; |
| 117 | return line; |
| 118 | }; |
| 119 | |
| 120 | // RCT_EXPORT_METHOD(selectorFirstKw:(args)…) |
| 121 | // The first keyword (everything up to the first `:` or open paren) is the |
| 122 | // JS-visible name. We don't try to parse full multi-keyword selectors — |
| 123 | // RN's JS view of the method uses only the first keyword. |
| 124 | const exportRegex = /RCT_EXPORT_METHOD\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)/g; |
| 125 | let m: RegExpExecArray | null; |
| 126 | while ((m = exportRegex.exec(source)) !== null) { |
| 127 | const kw = m[1]; |
| 128 | if (kw) results.push({ moduleName, jsName: kw, nativeSelectorFirstKw: kw, line: lineOf(m.index) }); |
| 129 | } |
| 130 | |
| 131 | // RCT_REMAP_METHOD(jsName, nativeSelectorFirstKw:(args)…) |
| 132 | const remapRegex = |
| 133 | /RCT_REMAP_METHOD\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,\s*([A-Za-z_][A-Za-z0-9_]*)/g; |
| 134 | while ((m = remapRegex.exec(source)) !== null) { |
| 135 | const jsName = m[1]; |
| 136 | const nativeKw = m[2]; |
| 137 | if (jsName && nativeKw) { |
| 138 | results.push({ moduleName, jsName, nativeSelectorFirstKw: nativeKw, line: lineOf(m.index) }); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | return results; |
| 143 | } |
| 144 | |
| 145 | /** |
| 146 | * Find the `@implementation` class name in an ObjC file — used as the |
no test coverage detected