@@ -63,6 +63,24 @@ interface AnnotationPluginPass extends PluginPass {
6363
6464type AnnotationPlugin = PluginObj < AnnotationPluginPass > ;
6565
66+ // Shared context object for all JSX processing functions
67+ interface JSXProcessingContext {
68+ /** Whether to annotate React fragments */
69+ annotateFragments : boolean ;
70+ /** Babel types object */
71+ t : typeof Babel . types ;
72+ /** Name of the React component */
73+ componentName : string ;
74+ /** Source file name (optional) */
75+ sourceFileName ?: string ;
76+ /** Array of attribute names [component, element, sourceFile] */
77+ attributeNames : string [ ] ;
78+ /** Array of component names to ignore */
79+ ignoredComponents : string [ ] ;
80+ /** Fragment context for identifying React fragments */
81+ fragmentContext ?: FragmentContext ;
82+ }
83+
6684// We must export the plugin as default, otherwise the Babel loader will not be able to resolve it when configured using its string identifier
6785export default function componentNameAnnotatePlugin ( { types : t } : typeof Babel ) : AnnotationPlugin {
6886 return {
@@ -81,16 +99,8 @@ export default function componentNameAnnotatePlugin({ types: t }: typeof Babel):
8199 return ;
82100 }
83101
84- functionBodyPushAttributes (
85- state . opts [ "annotate-fragments" ] === true ,
86- t ,
87- path ,
88- path . node . id . name ,
89- sourceFileNameFromState ( state ) ,
90- attributeNamesFromState ( state ) ,
91- state . opts . ignoredComponents ?? [ ] ,
92- state . sentryFragmentContext
93- ) ;
102+ const context = createJSXProcessingContext ( state , t , path . node . id . name ) ;
103+ functionBodyPushAttributes ( context , path ) ;
94104 } ,
95105 ArrowFunctionExpression ( path , state ) {
96106 // We're expecting a `VariableDeclarator` like `const MyComponent =`
@@ -110,16 +120,8 @@ export default function componentNameAnnotatePlugin({ types: t }: typeof Babel):
110120 return ;
111121 }
112122
113- functionBodyPushAttributes (
114- state . opts [ "annotate-fragments" ] === true ,
115- t ,
116- path ,
117- parent . id . name ,
118- sourceFileNameFromState ( state ) ,
119- attributeNamesFromState ( state ) ,
120- state . opts . ignoredComponents ?? [ ] ,
121- state . sentryFragmentContext
122- ) ;
123+ const context = createJSXProcessingContext ( state , t , parent . id . name ) ;
124+ functionBodyPushAttributes ( context , path ) ;
123125 } ,
124126 ClassDeclaration ( path , state ) {
125127 const name = path . get ( "id" ) ;
@@ -132,7 +134,7 @@ export default function componentNameAnnotatePlugin({ types: t }: typeof Babel):
132134 return ;
133135 }
134136
135- const ignoredComponents = state . opts . ignoredComponents ?? [ ] ;
137+ const context = createJSXProcessingContext ( state , t , name . node ?. name || "" ) ;
136138
137139 render . traverse ( {
138140 ReturnStatement ( returnStatement ) {
@@ -142,32 +144,41 @@ export default function componentNameAnnotatePlugin({ types: t }: typeof Babel):
142144 return ;
143145 }
144146
145- processJSX (
146- state . opts [ "annotate-fragments" ] === true ,
147- t ,
148- arg ,
149- name . node && name . node . name ,
150- sourceFileNameFromState ( state ) ,
151- attributeNamesFromState ( state ) ,
152- ignoredComponents ,
153- state . sentryFragmentContext
154- ) ;
147+ processJSX ( context , arg ) ;
155148 } ,
156149 } ) ;
157150 } ,
158151 } ,
159152 } ;
160153}
161154
162- function functionBodyPushAttributes (
163- annotateFragments : boolean ,
155+ /**
156+ * Creates a JSX processing context from the plugin state
157+ */
158+ function createJSXProcessingContext (
159+ state : AnnotationPluginPass ,
164160 t : typeof Babel . types ,
165- path : Babel . NodePath < Babel . types . Function > ,
166- componentName : string ,
167- sourceFileName : string | undefined ,
168- attributeNames : string [ ] ,
169- ignoredComponents : string [ ] ,
170- fragmentContext ?: FragmentContext
161+ componentName : string
162+ ) : JSXProcessingContext {
163+ return {
164+ annotateFragments : state . opts [ "annotate-fragments" ] === true ,
165+ t,
166+ componentName,
167+ sourceFileName : sourceFileNameFromState ( state ) ,
168+ attributeNames : attributeNamesFromState ( state ) ,
169+ ignoredComponents : state . opts . ignoredComponents ?? [ ] ,
170+ fragmentContext : state . sentryFragmentContext ,
171+ } ;
172+ }
173+
174+ /**
175+ * Processes the body of a function to add Sentry tracking attributes to JSX elements.
176+ * Handles various function body structures including direct JSX returns, conditional expressions,
177+ * and nested JSX elements.
178+ */
179+ function functionBodyPushAttributes (
180+ context : JSXProcessingContext ,
181+ path : Babel . NodePath < Babel . types . Function >
171182) : void {
172183 let jsxNode : Babel . NodePath ;
173184
@@ -209,29 +220,11 @@ function functionBodyPushAttributes(
209220 if ( arg . isConditionalExpression ( ) ) {
210221 const consequent = arg . get ( "consequent" ) ;
211222 if ( consequent . isJSXFragment ( ) || consequent . isJSXElement ( ) ) {
212- processJSX (
213- annotateFragments ,
214- t ,
215- consequent ,
216- componentName ,
217- sourceFileName ,
218- attributeNames ,
219- ignoredComponents ,
220- fragmentContext
221- ) ;
223+ processJSX ( context , consequent ) ;
222224 }
223225 const alternate = arg . get ( "alternate" ) ;
224226 if ( alternate . isJSXFragment ( ) || alternate . isJSXElement ( ) ) {
225- processJSX (
226- annotateFragments ,
227- t ,
228- alternate ,
229- componentName ,
230- sourceFileName ,
231- attributeNames ,
232- ignoredComponents ,
233- fragmentContext
234- ) ;
227+ processJSX ( context , alternate ) ;
235228 }
236229 return ;
237230 }
@@ -247,45 +240,36 @@ function functionBodyPushAttributes(
247240 return ;
248241 }
249242
250- processJSX (
251- annotateFragments ,
252- t ,
253- jsxNode ,
254- componentName ,
255- sourceFileName ,
256- attributeNames ,
257- ignoredComponents ,
258- fragmentContext
259- ) ;
243+ processJSX ( context , jsxNode ) ;
260244}
261245
246+ /**
247+ * Recursively processes JSX elements to add Sentry tracking attributes.
248+ * Handles both JSX elements and fragments, applying appropriate attributes
249+ * based on configuration and component context.
250+ */
262251function processJSX (
263- annotateFragments : boolean ,
264- t : typeof Babel . types ,
252+ context : JSXProcessingContext ,
265253 jsxNode : Babel . NodePath ,
266- componentName : string | null ,
267- sourceFileName : string | undefined ,
268- attributeNames : string [ ] ,
269- ignoredComponents : string [ ] ,
270- fragmentContext ?: FragmentContext
254+ componentName ?: string | null
271255) : void {
272256 if ( ! jsxNode ) {
273257 return ;
274258 }
259+
260+ // Use provided componentName or fall back to context componentName
261+ const currentComponentName = componentName !== undefined ? componentName : context . componentName ;
262+
275263 // NOTE: I don't know of a case where `openingElement` would have more than one item,
276264 // but it's safer to always iterate
277265 const paths = jsxNode . get ( "openingElement" ) ;
278266 const openingElements = Array . isArray ( paths ) ? paths : [ paths ] ;
279267
280268 openingElements . forEach ( ( openingElement ) => {
281269 applyAttributes (
282- t ,
270+ context ,
283271 openingElement as Babel . NodePath < Babel . types . JSXOpeningElement > ,
284- componentName ,
285- sourceFileName ,
286- attributeNames ,
287- ignoredComponents ,
288- fragmentContext
272+ currentComponentName
289273 ) ;
290274 } ) ;
291275
@@ -296,7 +280,7 @@ function processJSX(
296280 children = [ children ] ;
297281 }
298282
299- let shouldSetComponentName = annotateFragments ;
283+ let shouldSetComponentName = context . annotateFragments ;
300284
301285 children . forEach ( ( child ) => {
302286 // Happens for some node types like plain text
@@ -314,40 +298,24 @@ function processJSX(
314298
315299 if ( shouldSetComponentName && openingElement && openingElement . node ) {
316300 shouldSetComponentName = false ;
317- processJSX (
318- annotateFragments ,
319- t ,
320- child ,
321- componentName ,
322- sourceFileName ,
323- attributeNames ,
324- ignoredComponents ,
325- fragmentContext
326- ) ;
301+ processJSX ( context , child , currentComponentName ) ;
327302 } else {
328- processJSX (
329- annotateFragments ,
330- t ,
331- child ,
332- null ,
333- sourceFileName ,
334- attributeNames ,
335- ignoredComponents ,
336- fragmentContext
337- ) ;
303+ processJSX ( context , child , null ) ;
338304 }
339305 } ) ;
340306}
341307
308+ /**
309+ * Applies Sentry tracking attributes to a JSX opening element.
310+ * Adds component name, element name, and source file attributes while
311+ * respecting ignore lists and fragment detection.
312+ */
342313function applyAttributes (
343- t : typeof Babel . types ,
314+ context : JSXProcessingContext ,
344315 openingElement : Babel . NodePath < Babel . types . JSXOpeningElement > ,
345- componentName : string | null ,
346- sourceFileName : string | undefined ,
347- attributeNames : string [ ] ,
348- ignoredComponents : string [ ] ,
349- fragmentContext ?: FragmentContext
316+ componentName : string | null
350317) : void {
318+ const { t, attributeNames, ignoredComponents, fragmentContext, sourceFileName } = context ;
351319 const [ componentAttributeName , elementAttributeName , sourceFileAttributeName ] = attributeNames ;
352320
353321 // e.g., Raw JSX text like the `A` in `<h1>a</h1>`
0 commit comments