@@ -42,7 +42,29 @@ export type TransformImport = {
4242 range : SourceRange | null
4343}
4444
45- export type TransformJsxSourceOptions = TranspileJsxSourceOptions
45+ export type TransformTopLevelDeclarationKind = 'function' | 'class' | 'variable'
46+
47+ export type TransformTopLevelDeclarationExportKind = 'none' | 'named' | 'default'
48+
49+ export type TransformVariableInitializerKind =
50+ | 'arrow-function'
51+ | 'function-expression'
52+ | 'class-expression'
53+ | 'other'
54+ | null
55+
56+ export type TransformTopLevelDeclaration = {
57+ name : string
58+ kind : TransformTopLevelDeclarationKind
59+ exportKind : TransformTopLevelDeclarationExportKind
60+ range : SourceRange | null
61+ statementRange : SourceRange | null
62+ initializerKind : TransformVariableInitializerKind
63+ }
64+
65+ export type TransformJsxSourceOptions = TranspileJsxSourceOptions & {
66+ collectTopLevelDeclarations ?: boolean
67+ }
4668
4769type InternalTransformJsxSourceOptions = TransformJsxSourceOptions & {
4870 /* Internal compare switch for parity spikes. */
@@ -54,6 +76,7 @@ export type TransformJsxSourceResult = {
5476 changed : boolean
5577 imports : TransformImport [ ]
5678 diagnostics : TransformDiagnostic [ ]
79+ declarations ?: TransformTopLevelDeclaration [ ]
5780}
5881
5982const createParserOptions = ( sourceType : TransformSourceType ) => ( {
@@ -214,6 +237,163 @@ const collectImportMetadata = (body: unknown): TransformImport[] => {
214237 return imports
215238}
216239
240+ const toIdentifierName = ( value : unknown ) : string | null => {
241+ if ( ! isObjectRecord ( value ) ) {
242+ return null
243+ }
244+
245+ if ( value . type !== 'Identifier' ) {
246+ return null
247+ }
248+
249+ return typeof value . name === 'string' ? value . name : null
250+ }
251+
252+ const toVariableInitializerKind = ( value : unknown ) : TransformVariableInitializerKind => {
253+ if ( ! isObjectRecord ( value ) || typeof value . type !== 'string' ) {
254+ return null
255+ }
256+
257+ if ( value . type === 'ArrowFunctionExpression' ) {
258+ return 'arrow-function'
259+ }
260+
261+ if ( value . type === 'FunctionExpression' ) {
262+ return 'function-expression'
263+ }
264+
265+ if ( value . type === 'ClassExpression' ) {
266+ return 'class-expression'
267+ }
268+
269+ return 'other'
270+ }
271+
272+ const pushTopLevelDeclarationMetadata = ( {
273+ declaration,
274+ exportKind,
275+ statementRange,
276+ declarations,
277+ } : {
278+ declaration : Record < string , unknown >
279+ exportKind : TransformTopLevelDeclarationExportKind
280+ statementRange : SourceRange | null
281+ declarations : TransformTopLevelDeclaration [ ]
282+ } ) => {
283+ if ( declaration . type === 'FunctionDeclaration' ) {
284+ const name = toIdentifierName ( declaration . id )
285+ if ( ! name ) {
286+ return
287+ }
288+
289+ declarations . push ( {
290+ name,
291+ kind : 'function' ,
292+ exportKind,
293+ range : toSourceRange ( declaration ) ,
294+ statementRange,
295+ initializerKind : null ,
296+ } )
297+ return
298+ }
299+
300+ if ( declaration . type === 'ClassDeclaration' ) {
301+ const name = toIdentifierName ( declaration . id )
302+ if ( ! name ) {
303+ return
304+ }
305+
306+ declarations . push ( {
307+ name,
308+ kind : 'class' ,
309+ exportKind,
310+ range : toSourceRange ( declaration ) ,
311+ statementRange,
312+ initializerKind : null ,
313+ } )
314+ return
315+ }
316+
317+ if ( ! Array . isArray ( declaration . declarations ) ) {
318+ return
319+ }
320+
321+ for ( const declarator of declaration . declarations ) {
322+ if ( ! isObjectRecord ( declarator ) ) {
323+ continue
324+ }
325+
326+ const name = toIdentifierName ( declarator . id )
327+ if ( ! name ) {
328+ continue
329+ }
330+
331+ declarations . push ( {
332+ name,
333+ kind : 'variable' ,
334+ exportKind,
335+ range : toSourceRange ( declarator ) ,
336+ statementRange,
337+ initializerKind : toVariableInitializerKind ( declarator . init ) ,
338+ } )
339+ }
340+ }
341+
342+ const collectTopLevelDeclarationMetadata = (
343+ body : unknown ,
344+ ) : TransformTopLevelDeclaration [ ] => {
345+ if ( ! Array . isArray ( body ) ) {
346+ return [ ]
347+ }
348+
349+ const declarations : TransformTopLevelDeclaration [ ] = [ ]
350+
351+ for ( const statement of body ) {
352+ if ( ! isObjectRecord ( statement ) || typeof statement . type !== 'string' ) {
353+ continue
354+ }
355+
356+ const statementRange = toSourceRange ( statement )
357+
358+ if ( statement . type === 'ExportNamedDeclaration' ) {
359+ if ( ! isObjectRecord ( statement . declaration ) ) {
360+ continue
361+ }
362+
363+ pushTopLevelDeclarationMetadata ( {
364+ declaration : statement . declaration ,
365+ exportKind : 'named' ,
366+ statementRange,
367+ declarations,
368+ } )
369+ continue
370+ }
371+
372+ if ( statement . type === 'ExportDefaultDeclaration' ) {
373+ if ( ! isObjectRecord ( statement . declaration ) ) {
374+ continue
375+ }
376+
377+ pushTopLevelDeclarationMetadata ( {
378+ declaration : statement . declaration ,
379+ exportKind : 'default' ,
380+ statementRange,
381+ declarations,
382+ } )
383+ continue
384+ }
385+
386+ pushTopLevelDeclarationMetadata ( {
387+ declaration : statement ,
388+ exportKind : 'none' ,
389+ statementRange,
390+ declarations,
391+ } )
392+ }
393+
394+ return declarations
395+ }
396+
217397const ensureSupportedOptions = ( options : InternalTransformJsxSourceOptions ) => {
218398 if (
219399 options . sourceType !== undefined &&
@@ -244,6 +424,15 @@ const ensureSupportedOptions = (options: InternalTransformJsxSourceOptions) => {
244424 `[jsx] Unsupported typescriptStripBackend "${ String ( options . typescriptStripBackend ) } ". Use "oxc-transform" or "transpile-manual".` ,
245425 )
246426 }
427+
428+ if (
429+ options . collectTopLevelDeclarations !== undefined &&
430+ typeof options . collectTopLevelDeclarations !== 'boolean'
431+ ) {
432+ throw new Error (
433+ `[jsx] Unsupported collectTopLevelDeclarations value "${ String ( options . collectTopLevelDeclarations ) } ". Use true or false.` ,
434+ )
435+ }
247436}
248437
249438export function transformJsxSource (
@@ -266,13 +455,17 @@ export function transformJsxSource(
266455
267456 const parserDiagnostics = parsed . errors . map ( error => toDiagnostic ( 'parser' , error ) )
268457 const imports = collectImportMetadata ( parsed . program . body )
458+ const declarations = internalOptions . collectTopLevelDeclarations
459+ ? collectTopLevelDeclarationMetadata ( parsed . program . body )
460+ : undefined
269461
270462 if ( parserDiagnostics . length ) {
271463 return {
272464 code : source ,
273465 changed : false ,
274466 imports,
275467 diagnostics : parserDiagnostics ,
468+ declarations,
276469 }
277470 }
278471
@@ -290,6 +483,7 @@ export function transformJsxSource(
290483 changed : result . changed ,
291484 imports,
292485 diagnostics : parserDiagnostics ,
486+ declarations,
293487 }
294488 }
295489
@@ -304,6 +498,7 @@ export function transformJsxSource(
304498 changed : result . changed ,
305499 imports,
306500 diagnostics : parserDiagnostics ,
501+ declarations,
307502 }
308503 }
309504
@@ -326,6 +521,7 @@ export function transformJsxSource(
326521 changed : fallbackCode !== source ,
327522 imports,
328523 diagnostics,
524+ declarations,
329525 }
330526 }
331527
@@ -336,5 +532,6 @@ export function transformJsxSource(
336532 changed : jsxResult . code !== source ,
337533 imports,
338534 diagnostics,
535+ declarations,
339536 }
340537}
0 commit comments