@@ -63,6 +63,24 @@ interface AnnotationPluginPass extends PluginPass {
63
63
64
64
type AnnotationPlugin = PluginObj < AnnotationPluginPass > ;
65
65
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
+
66
84
// We must export the plugin as default, otherwise the Babel loader will not be able to resolve it when configured using its string identifier
67
85
export default function componentNameAnnotatePlugin ( { types : t } : typeof Babel ) : AnnotationPlugin {
68
86
return {
@@ -81,16 +99,8 @@ export default function componentNameAnnotatePlugin({ types: t }: typeof Babel):
81
99
return ;
82
100
}
83
101
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 ) ;
94
104
} ,
95
105
ArrowFunctionExpression ( path , state ) {
96
106
// We're expecting a `VariableDeclarator` like `const MyComponent =`
@@ -110,16 +120,8 @@ export default function componentNameAnnotatePlugin({ types: t }: typeof Babel):
110
120
return ;
111
121
}
112
122
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 ) ;
123
125
} ,
124
126
ClassDeclaration ( path , state ) {
125
127
const name = path . get ( "id" ) ;
@@ -132,7 +134,7 @@ export default function componentNameAnnotatePlugin({ types: t }: typeof Babel):
132
134
return ;
133
135
}
134
136
135
- const ignoredComponents = state . opts . ignoredComponents ?? [ ] ;
137
+ const context = createJSXProcessingContext ( state , t , name . node ?. name || "" ) ;
136
138
137
139
render . traverse ( {
138
140
ReturnStatement ( returnStatement ) {
@@ -142,32 +144,41 @@ export default function componentNameAnnotatePlugin({ types: t }: typeof Babel):
142
144
return ;
143
145
}
144
146
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 ) ;
155
148
} ,
156
149
} ) ;
157
150
} ,
158
151
} ,
159
152
} ;
160
153
}
161
154
162
- function functionBodyPushAttributes (
163
- annotateFragments : boolean ,
155
+ /**
156
+ * Creates a JSX processing context from the plugin state
157
+ */
158
+ function createJSXProcessingContext (
159
+ state : AnnotationPluginPass ,
164
160
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 >
171
182
) : void {
172
183
let jsxNode : Babel . NodePath ;
173
184
@@ -209,29 +220,11 @@ function functionBodyPushAttributes(
209
220
if ( arg . isConditionalExpression ( ) ) {
210
221
const consequent = arg . get ( "consequent" ) ;
211
222
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 ) ;
222
224
}
223
225
const alternate = arg . get ( "alternate" ) ;
224
226
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 ) ;
235
228
}
236
229
return ;
237
230
}
@@ -247,45 +240,36 @@ function functionBodyPushAttributes(
247
240
return ;
248
241
}
249
242
250
- processJSX (
251
- annotateFragments ,
252
- t ,
253
- jsxNode ,
254
- componentName ,
255
- sourceFileName ,
256
- attributeNames ,
257
- ignoredComponents ,
258
- fragmentContext
259
- ) ;
243
+ processJSX ( context , jsxNode ) ;
260
244
}
261
245
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
+ */
262
251
function processJSX (
263
- annotateFragments : boolean ,
264
- t : typeof Babel . types ,
252
+ context : JSXProcessingContext ,
265
253
jsxNode : Babel . NodePath ,
266
- componentName : string | null ,
267
- sourceFileName : string | undefined ,
268
- attributeNames : string [ ] ,
269
- ignoredComponents : string [ ] ,
270
- fragmentContext ?: FragmentContext
254
+ componentName ?: string | null
271
255
) : void {
272
256
if ( ! jsxNode ) {
273
257
return ;
274
258
}
259
+
260
+ // Use provided componentName or fall back to context componentName
261
+ const currentComponentName = componentName !== undefined ? componentName : context . componentName ;
262
+
275
263
// NOTE: I don't know of a case where `openingElement` would have more than one item,
276
264
// but it's safer to always iterate
277
265
const paths = jsxNode . get ( "openingElement" ) ;
278
266
const openingElements = Array . isArray ( paths ) ? paths : [ paths ] ;
279
267
280
268
openingElements . forEach ( ( openingElement ) => {
281
269
applyAttributes (
282
- t ,
270
+ context ,
283
271
openingElement as Babel . NodePath < Babel . types . JSXOpeningElement > ,
284
- componentName ,
285
- sourceFileName ,
286
- attributeNames ,
287
- ignoredComponents ,
288
- fragmentContext
272
+ currentComponentName
289
273
) ;
290
274
} ) ;
291
275
@@ -296,7 +280,7 @@ function processJSX(
296
280
children = [ children ] ;
297
281
}
298
282
299
- let shouldSetComponentName = annotateFragments ;
283
+ let shouldSetComponentName = context . annotateFragments ;
300
284
301
285
children . forEach ( ( child ) => {
302
286
// Happens for some node types like plain text
@@ -314,40 +298,24 @@ function processJSX(
314
298
315
299
if ( shouldSetComponentName && openingElement && openingElement . node ) {
316
300
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 ) ;
327
302
} 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 ) ;
338
304
}
339
305
} ) ;
340
306
}
341
307
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
+ */
342
313
function applyAttributes (
343
- t : typeof Babel . types ,
314
+ context : JSXProcessingContext ,
344
315
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
350
317
) : void {
318
+ const { t, attributeNames, ignoredComponents, fragmentContext, sourceFileName } = context ;
351
319
const [ componentAttributeName , elementAttributeName , sourceFileAttributeName ] = attributeNames ;
352
320
353
321
// e.g., Raw JSX text like the `A` in `<h1>a</h1>`
0 commit comments