* Parse the first non-internal frame from an error stack string.
( stack: string )
| 484 | * Parse the first non-internal frame from an error stack string. |
| 485 | */ |
| 486 | function parseComponentFrame( |
| 487 | stack: string |
| 488 | ): { fileName: string; line: number; column?: number } | null { |
| 489 | const lines = stack.split("\n"); |
| 490 | |
| 491 | // Patterns to skip: our own bundle, React internals, node_modules, chunk files |
| 492 | const skipPatterns = [ |
| 493 | /source-location/, |
| 494 | /\/dist\/index\./, // Our bundled output (dist/index.mjs, dist/index.js) |
| 495 | /node_modules\//, // Any package in node_modules |
| 496 | /react-dom/, |
| 497 | /react\.development/, |
| 498 | /react\.production/, |
| 499 | /chunk-[A-Z0-9]+/i, |
| 500 | /react-stack-bottom-frame/, |
| 501 | /react-reconciler/, |
| 502 | /scheduler/, |
| 503 | /<anonymous>/, // Proxy handler frames |
| 504 | ]; |
| 505 | |
| 506 | // V8 format: " at FnName (file:line:col)" or " at file:line:col" |
| 507 | const v8Re = /^\s*at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?$/; |
| 508 | // WebKit/Gecko: "FnName@file:line:col" or "@file:line:col" |
| 509 | const webkitRe = /^[^@]*@(.+?):(\d+):(\d+)$/; |
| 510 | |
| 511 | for (const line of lines) { |
| 512 | const trimmed = line.trim(); |
| 513 | if (!trimmed) continue; |
| 514 | |
| 515 | // Skip frames from internal files |
| 516 | if (skipPatterns.some((p) => p.test(trimmed))) continue; |
| 517 | |
| 518 | const match = v8Re.exec(trimmed) || webkitRe.exec(trimmed); |
| 519 | if (match) { |
| 520 | return { |
| 521 | fileName: match[1], |
| 522 | line: parseInt(match[2], 10), |
| 523 | column: parseInt(match[3], 10), |
| 524 | }; |
| 525 | } |
| 526 | } |
| 527 | |
| 528 | return null; |
| 529 | } |
| 530 | |
| 531 | /** |
| 532 | * Strip bundler URL prefixes from a raw source path. |
no outgoing calls
no test coverage detected
searching dependent graphs…