( filePath: string, content: string )
| 178 | // ─── Jetpack Compose Component Extraction ─── |
| 179 | |
| 180 | export function extractComposeComponents( |
| 181 | filePath: string, |
| 182 | content: string |
| 183 | ): ComponentInfo[] { |
| 184 | if (!content.includes("@Composable")) return []; |
| 185 | |
| 186 | const components: ComponentInfo[] = []; |
| 187 | const seen = new Set<string>(); |
| 188 | |
| 189 | // Match @Composable fun ComponentName(params) |
| 190 | const composablePat = |
| 191 | /@Composable\s+(?:(?:private|internal|public)\s+)?fun\s+([A-Z]\w*)\s*\(([^)]*)\)/gs; |
| 192 | let m: RegExpExecArray | null; |
| 193 | |
| 194 | while ((m = composablePat.exec(content)) !== null) { |
| 195 | const name = m[1]; |
| 196 | if (seen.has(name)) continue; |
| 197 | seen.add(name); |
| 198 | |
| 199 | const paramsStr = m[2]; |
| 200 | const props: string[] = []; |
| 201 | |
| 202 | // Parse params, skip Modifier |
| 203 | const paramPat = /(\w+)\s*:\s*([\w<>().,?\s*]+?)(?:\s*=\s*[^,)]+)?(?:,|$)/g; |
| 204 | let pm: RegExpExecArray | null; |
| 205 | while ((pm = paramPat.exec(paramsStr)) !== null) { |
| 206 | const paramName = pm[1].trim(); |
| 207 | const paramType = pm[2].trim(); |
| 208 | if (paramName === "modifier" || paramType === "Modifier") continue; |
| 209 | if (paramName) props.push(paramName); |
| 210 | } |
| 211 | |
| 212 | components.push({ |
| 213 | name, |
| 214 | file: filePath, |
| 215 | props, |
| 216 | isClient: true, |
| 217 | isServer: false, |
| 218 | confidence: "regex", |
| 219 | }); |
| 220 | } |
| 221 | |
| 222 | return components; |
| 223 | } |
| 224 | |
| 225 | // ─── Navigation XML Route Extraction ─── |
| 226 |
no outgoing calls
no test coverage detected