* Resolve component info from a DOM element using bippy's owner stack. * Handles both React 18 (_debugSource) and React 19 (owner stacks + source maps).
(el: HTMLElement)
| 60 | * Handles both React 18 (_debugSource) and React 19 (owner stacks + source maps). |
| 61 | */ |
| 62 | async function resolveComponentFromElement(el: HTMLElement): Promise<ResolvedComponent | null> { |
| 63 | const fiber = getFiberFromHostInstance(el); |
| 64 | if (!fiber) { |
| 65 | const cloneResolution = resolveFromCloneAncestry(el); |
| 66 | if (cloneResolution) { |
| 67 | const { sourceInfo } = cloneResolution; |
| 68 | return { |
| 69 | tagName: el.tagName.toLowerCase(), |
| 70 | componentName: sourceInfo.componentName, |
| 71 | filePath: sourceInfo.filePath, |
| 72 | lineNumber: sourceInfo.lineNumber, |
| 73 | columnNumber: sourceInfo.columnNumber, |
| 74 | stack: sourceInfo.stack, |
| 75 | jsxPath: sourceInfo.jsxPath, |
| 76 | }; |
| 77 | } |
| 78 | return null; |
| 79 | } |
| 80 | |
| 81 | // Try bippy/source getOwnerStack first — handles React 19 owner stacks with symbolication |
| 82 | try { |
| 83 | const frames = await getOwnerStack(fiber); |
| 84 | if (frames && frames.length > 0) { |
| 85 | const stack: ResolvedComponent["stack"] = []; |
| 86 | for (const frame of frames) { |
| 87 | if (!frame.functionName) continue; |
| 88 | const name = frame.functionName; |
| 89 | if (name[0] !== name[0].toUpperCase()) continue; |
| 90 | |
| 91 | const filePath = resolveFrameFilePath(frame.fileName); |
| 92 | |
| 93 | // MDX content files: accept immediately — component name is synthetic |
| 94 | if (!(filePath && isMdxFilePath(filePath)) && isInternalName(name)) continue; |
| 95 | |
| 96 | stack.push({ |
| 97 | componentName: name, |
| 98 | filePath, |
| 99 | lineNumber: frame.lineNumber ?? 0, |
| 100 | columnNumber: frame.columnNumber ?? 0, |
| 101 | }); |
| 102 | } |
| 103 | |
| 104 | if (stack.length > 0) { |
| 105 | // Prefer the first frame with a resolved file path (skip library wrappers) |
| 106 | const primary = stack.find(f => f.filePath) || stack[0]; |
| 107 | const result: ResolvedComponent = { |
| 108 | tagName: el.tagName.toLowerCase(), |
| 109 | componentName: primary.componentName, |
| 110 | filePath: primary.filePath, |
| 111 | lineNumber: primary.lineNumber, |
| 112 | columnNumber: primary.columnNumber, |
| 113 | stack, |
| 114 | }; |
| 115 | result.jsxPath = buildJSXPath(el, primary.filePath, primary.componentName) ?? undefined; |
| 116 | return result; |
| 117 | } |
| 118 | } |
| 119 | } catch (err) { |
no test coverage detected