@@ -54,6 +54,38 @@ export function emitStylesAndImports(args: {
5454 addHeaderComments ( ( n as any ) ?. comments ) ;
5555 }
5656
57+ const isBugComment = ( c : any ) : boolean => {
58+ const v = typeof c ?. value === "string" ? String ( c . value ) . trim ( ) : "" ;
59+ // Treat "Bug N:" fixture narrative comments as file-level comments, not style-property docs.
60+ return / ^ B u g \s + \d + [ a - z A - Z ] ? \s * : / . test ( v ) ;
61+ } ;
62+
63+ const splitBugNarrativeLeadingComments = (
64+ comments : unknown ,
65+ ) : { narrative : any [ ] ; property : any [ ] } => {
66+ if ( ! Array . isArray ( comments ) || comments . length === 0 ) {
67+ return { narrative : [ ] , property : [ ] } ;
68+ }
69+ let bugIdx = - 1 ;
70+ for ( let i = 0 ; i < comments . length ; i ++ ) {
71+ if ( isBugComment ( ( comments as any [ ] ) [ i ] ) ) {
72+ bugIdx = i ;
73+ break ;
74+ }
75+ }
76+ if ( bugIdx < 0 ) {
77+ return { narrative : [ ] , property : comments as any [ ] } ;
78+ }
79+ // Include the full contiguous comment array from the first "Bug ..." comment onward.
80+ // This captures follow-up lines like:
81+ // // Bug N: ...
82+ // // more context...
83+ return {
84+ narrative : ( comments as any [ ] ) . slice ( bugIdx ) ,
85+ property : ( comments as any [ ] ) . slice ( 0 , bugIdx ) ,
86+ } ;
87+ } ;
88+
5789 // Remove styled-components import(s), but preserve any named imports that are still referenced
5890 // (e.g. useTheme, withTheme, ThemeProvider if they're still used in the code)
5991 const preservedSpecifiers : string [ ] = [ ] ;
@@ -150,6 +182,65 @@ export function emitStylesAndImports(args: {
150182 }
151183 }
152184
185+ // Preserve leading comments that sit on the *styled declaration statement* itself.
186+ //
187+ // These often include fixture-level explanations (e.g. "Bug N: ...") that are attached to
188+ // `export const X = styled...` declarations. Since we remove those declarations later in the
189+ // transform, we need to migrate their leading comments onto a node that remains (the emitted
190+ // `const styles = stylex.create(...)` declaration is the best anchor).
191+ //
192+ // Important: we avoid duplicating comments that are already being preserved as style property
193+ // comments via `StyledDecl.leadingComments`.
194+ const propCommentKeys = new Set < string > ( ) ;
195+ for ( const decl of styledDecls ) {
196+ const cs = ( decl as any ) . leadingComments ;
197+ if ( ! Array . isArray ( cs ) ) {
198+ continue ;
199+ }
200+ const { property } = splitBugNarrativeLeadingComments ( cs ) ;
201+ for ( const c of property ) {
202+ const key = `${ ( c as any ) ?. type ?? "Comment" } :${ String ( ( c as any ) ?. value ?? "" ) . trim ( ) } ` ;
203+ propCommentKeys . add ( key ) ;
204+ }
205+ }
206+
207+ const migratedStyledDeclLeadingComments : any [ ] = [ ] ;
208+ // Prefer sourcing these from `StyledDecl.leadingComments` (captured from the original styled
209+ // declaration VariableDeclaration). This is more reliable than reading statement comments
210+ // because some parsers/printers split multi-line comment runs across different comment arrays.
211+ const declsByLoc = [ ...styledDecls ] . sort ( ( a , b ) => {
212+ const al = ( ( a as any ) ?. loc ?. start ?. line ?? Number . POSITIVE_INFINITY ) as number ;
213+ const bl = ( ( b as any ) ?. loc ?. start ?. line ?? Number . POSITIVE_INFINITY ) as number ;
214+ return al - bl ;
215+ } ) ;
216+ for ( const d of declsByLoc ) {
217+ const cs = ( d as any ) . leadingComments ;
218+ if ( ! Array . isArray ( cs ) || cs . length === 0 ) {
219+ continue ;
220+ }
221+ const { narrative } = splitBugNarrativeLeadingComments ( cs ) ;
222+ if ( narrative . length === 0 ) {
223+ continue ;
224+ }
225+ for ( const c of narrative ) {
226+ const key = `${ ( c as any ) ?. type ?? "Comment" } :${ String ( ( c as any ) ?. value ?? "" ) . trim ( ) } ` ;
227+ if ( propCommentKeys . has ( key ) ) {
228+ continue ;
229+ }
230+ if ( ( c as any ) ?. leading === false ) {
231+ continue ;
232+ }
233+ // Clone the comment node so we can safely reattach it even if the original
234+ // declaration node (that initially owned it) is later removed from the AST.
235+ migratedStyledDeclLeadingComments . push ( {
236+ ...( c as any ) ,
237+ leading : true ,
238+ trailing : false ,
239+ } ) ;
240+ }
241+ break ;
242+ }
243+
153244 // Inject resolver-provided imports (from adapter.resolveValue calls).
154245 {
155246 const toModuleSpecifier = ( from : ImportSource ) : string => {
@@ -265,7 +356,12 @@ export function emitStylesAndImports(args: {
265356 const styleKeyToComments = new Map < string , any [ ] > ( ) ;
266357 for ( const decl of styledDecls ) {
267358 if ( decl . leadingComments && decl . leadingComments . length > 0 ) {
268- styleKeyToComments . set ( decl . styleKey , decl . leadingComments ) ;
359+ // Avoid attaching "Bug N:" narrative comments to a specific style property inside
360+ // `stylex.create({ ... })` — those belong above the `styles` declaration instead.
361+ const { property } = splitBugNarrativeLeadingComments ( decl . leadingComments ) ;
362+ if ( property . length > 0 ) {
363+ styleKeyToComments . set ( decl . styleKey , property ) ;
364+ }
269365 }
270366 }
271367
@@ -298,6 +394,28 @@ export function emitStylesAndImports(args: {
298394 ) ,
299395 ] ) ;
300396
397+ // Attach migrated leading comments (from the first styled declaration) to `styles`.
398+ if ( migratedStyledDeclLeadingComments . length > 0 ) {
399+ const merged = [
400+ ...migratedStyledDeclLeadingComments ,
401+ ...( Array . isArray ( ( stylesDecl as any ) . leadingComments )
402+ ? ( stylesDecl as any ) . leadingComments
403+ : [ ] ) ,
404+ ...( Array . isArray ( ( stylesDecl as any ) . comments ) ? ( stylesDecl as any ) . comments : [ ] ) ,
405+ ] as any [ ] ;
406+ const seen = new Set < string > ( ) ;
407+ const deduped = merged . filter ( ( c ) => {
408+ const key = `${ ( c as any ) ?. type ?? "Comment" } :${ String ( ( c as any ) ?. value ?? "" ) . trim ( ) } ` ;
409+ if ( seen . has ( key ) ) {
410+ return false ;
411+ }
412+ seen . add ( key ) ;
413+ return true ;
414+ } ) ;
415+ ( stylesDecl as any ) . leadingComments = deduped ;
416+ ( stylesDecl as any ) . comments = deduped ;
417+ }
418+
301419 // If styles reference identifiers declared later in the file (e.g. string-interpolation fixture),
302420 // insert `styles` after the last such declaration to satisfy StyleX evaluation order.
303421 const referencedIdents = new Set < string > ( ) ;
0 commit comments