11import * as t from '@babel/types' ;
2+ import { readFileSync } from 'fs' ;
23
34export function addFunctionComponentSourceLocationBabel ( ) {
45 function isReactFunctionName ( name ) {
56 // A React component function always starts with a Capital letter
67 return name && name . match ( / ^ [ A - Z ] .* / ) ;
78 }
89
10+ // Cache original file contents for finding correct line numbers.
11+ // When running after OXC (enforce: 'post'), Babel's AST positions
12+ // refer to the transformed code, not the original source. We read
13+ // the original file to get correct positions.
14+ const originalSources = { } ;
15+
16+ function getOriginalLines ( filename ) {
17+ if ( ! originalSources [ filename ] ) {
18+ try {
19+ originalSources [ filename ] = readFileSync ( filename , 'utf-8' ) . split ( '\n' ) ;
20+ } catch {
21+ originalSources [ filename ] = [ ] ;
22+ }
23+ }
24+ return originalSources [ filename ] ;
25+ }
26+
27+ function findFunctionLine ( filename , functionName ) {
28+ const lines = getOriginalLines ( filename ) ;
29+ for ( let i = 0 ; i < lines . length ; i ++ ) {
30+ // Match "function FunctionName(" or "const/let/var FunctionName ="
31+ const funcMatch = lines [ i ] . match ( new RegExp ( `\\bfunction\\s+${ functionName } \\s*\\(` ) ) ;
32+ const constMatch = lines [ i ] . match ( new RegExp ( `\\b(?:const|let|var)\\s+${ functionName } \\s*=` ) ) ;
33+ if ( funcMatch ) {
34+ // Find the opening brace of the function body
35+ const braceCol = lines [ i ] . indexOf ( '{' , funcMatch . index + funcMatch [ 0 ] . length ) ;
36+ if ( braceCol >= 0 ) {
37+ return { line : i + 1 , column : braceCol + 1 } ;
38+ }
39+ // Brace might be on a following line
40+ for ( let j = i + 1 ; j < lines . length ; j ++ ) {
41+ const bc = lines [ j ] . indexOf ( '{' ) ;
42+ if ( bc >= 0 ) return { line : j + 1 , column : bc + 1 } ;
43+ }
44+ }
45+ if ( constMatch ) {
46+ // Find arrow function body: look for => and then { or (
47+ for ( let j = i ; j < Math . min ( i + 5 , lines . length ) ; j ++ ) {
48+ const arrowIdx = lines [ j ] . indexOf ( '=>' ) ;
49+ if ( arrowIdx >= 0 ) {
50+ const after = lines [ j ] . substring ( arrowIdx + 2 ) . trim ( ) ;
51+ if ( after . startsWith ( '{' ) || after . startsWith ( '(' ) ) {
52+ return { line : j + 1 , column : arrowIdx + 3 } ;
53+ }
54+ if ( j + 1 < lines . length ) {
55+ return { line : j + 2 , column : 1 } ;
56+ }
57+ }
58+ }
59+ }
60+ }
61+ return null ;
62+ }
63+
964 /**
1065 * Writes debug info as Name.__debugSourceDefine={...} after the given statement ("path").
11- * This is used to make the source location of the function (defined by the loc parameter) available in the browser in development mode.
66+ * This is used to make the source location of the function available in the browser in development mode.
1267 * The name __debugSourceDefine is prefixed by __ to mark this is not a public API.
68+ *
69+ * Uses the original source file to determine correct line numbers,
70+ * since Babel may be running on OXC-transformed code with different positions.
1371 */
14- function addDebugInfo ( path , name , filename , loc ) {
15- const lineNumber = loc . start . line ;
16- const columnNumber = loc . start . column + 1 ;
72+ function addDebugInfo ( path , name , filename ) {
73+ const loc = findFunctionLine ( filename , name ) ;
74+ if ( ! loc ) {
75+ return ;
76+ }
1777 const debugSourceMember = t . memberExpression ( t . identifier ( name ) , t . identifier ( '__debugSourceDefine' ) ) ;
1878 const debugSourceDefine = t . objectExpression ( [
1979 t . objectProperty ( t . identifier ( 'fileName' ) , t . stringLiteral ( filename ) ) ,
20- t . objectProperty ( t . identifier ( 'lineNumber' ) , t . numericLiteral ( lineNumber ) ) ,
21- t . objectProperty ( t . identifier ( 'columnNumber' ) , t . numericLiteral ( columnNumber ) )
80+ t . objectProperty ( t . identifier ( 'lineNumber' ) , t . numericLiteral ( loc . line ) ) ,
81+ t . objectProperty ( t . identifier ( 'columnNumber' ) , t . numericLiteral ( loc . column ) )
2282 ] ) ;
2383 const assignment = t . expressionStatement ( t . assignmentExpression ( '=' , debugSourceMember , debugSourceDefine ) ) ;
2484 const condition = t . binaryExpression (
@@ -48,15 +108,13 @@ export function addFunctionComponentSourceLocationBabel() {
48108 }
49109
50110 const filename = state . file . opts . filename ;
51- if ( declaration ?. init ?. body ?. loc ) {
52- addDebugInfo ( path , name , filename , declaration . init . body . loc ) ;
53- }
111+ addDebugInfo ( path , name , filename ) ;
54112 } ) ;
55113 } ,
56114
57115 FunctionDeclaration ( path , state ) {
58116 // Finds declarations such as
59- // functio Foo() { return <div/>; }
117+ // function Foo() { return <div/>; }
60118 // export function Bar() { return <span>Hello</span>;}
61119
62120 // and writes a Foo.__debugSourceDefine= {..} after it, referring to the start of the function body
@@ -66,9 +124,7 @@ export function addFunctionComponentSourceLocationBabel() {
66124 return ;
67125 }
68126 const filename = state . file . opts . filename ;
69- if ( node . body . loc ) {
70- addDebugInfo ( path , name , filename , node . body . loc ) ;
71- }
127+ addDebugInfo ( path , name , filename ) ;
72128 }
73129 }
74130 } ;
0 commit comments