@@ -328,7 +328,7 @@ export class Php8Parser implements Parser {
328328 // ─── Comment annotation ────────────────────────────────────────
329329
330330 private annotateComments ( stmts : Node [ ] ) : void {
331- // Collect all comments from the token stream and assign them to adjacent nodes
331+ // Collect all comments from the token stream
332332 const comments : Comment [ ] = [ ] ;
333333 for ( const token of this . tokens ) {
334334 if ( token . id === T . T_COMMENT ) {
@@ -339,11 +339,73 @@ export class Php8Parser implements Parser {
339339 token . getEndLine ( ) , token . getEndPos ( ) - 1 , 0 ) ) ;
340340 }
341341 }
342- // Simple approach: assign comments to the first statement
343- if ( comments . length > 0 && stmts . length > 0 ) {
344- const firstNode = stmts [ 0 ] ;
345- if ( firstNode && typeof firstNode . setAttribute === 'function' ) {
346- firstNode . setAttribute ( 'comments' , comments ) ;
342+
343+ if ( comments . length === 0 ) return ;
344+
345+ // Collect all nodes in the AST with their file positions
346+ const allNodes : Node [ ] = [ ] ;
347+ this . collectAllNodes ( stmts , allNodes ) ;
348+
349+ // Sort nodes by startFilePos ascending
350+ allNodes . sort ( ( a , b ) => a . getStartFilePos ( ) - b . getStartFilePos ( ) ) ;
351+
352+ // For each comment, find the node whose startFilePos comes after the comment ends
353+ // and attach the comment to that node
354+ const nodeComments = new Map < Node , Comment [ ] > ( ) ;
355+
356+ for ( const comment of comments ) {
357+ const commentEndPos = comment . getEndFilePos ( ) ;
358+ // Binary search for the first node with startFilePos > commentEndPos
359+ let lo = 0 ;
360+ let hi = allNodes . length ;
361+ while ( lo < hi ) {
362+ const mid = ( lo + hi ) >>> 1 ;
363+ if ( allNodes [ mid ] . getStartFilePos ( ) <= commentEndPos ) {
364+ lo = mid + 1 ;
365+ } else {
366+ hi = mid ;
367+ }
368+ }
369+ if ( lo < allNodes . length ) {
370+ const targetNode = allNodes [ lo ] ;
371+ if ( ! nodeComments . has ( targetNode ) ) {
372+ nodeComments . set ( targetNode , [ ] ) ;
373+ }
374+ nodeComments . get ( targetNode ) ! . push ( comment ) ;
375+ }
376+ }
377+
378+ // Assign collected comments to their respective nodes
379+ for ( const [ node , nodeCommentList ] of nodeComments ) {
380+ node . setAttribute ( 'comments' , nodeCommentList ) ;
381+ }
382+ }
383+
384+ private collectAllNodes ( nodes : Node | Node [ ] , result : Node [ ] ) : void {
385+ if ( Array . isArray ( nodes ) ) {
386+ for ( const node of nodes ) {
387+ this . collectAllNodes ( node , result ) ;
388+ }
389+ return ;
390+ }
391+
392+ const node = nodes ;
393+ if ( ! node || typeof node . getType !== 'function' ) return ;
394+
395+ result . push ( node ) ;
396+
397+ for ( const rawName of node . getSubNodeNames ( ) ) {
398+ const name = ( node as any ) [ rawName ] !== undefined ? rawName : rawName + '_' ;
399+ const subNode = ( node as any ) [ name ] ;
400+ if ( subNode === null || subNode === undefined ) continue ;
401+ if ( Array . isArray ( subNode ) ) {
402+ for ( const item of subNode ) {
403+ if ( item && typeof item === 'object' && typeof item . getType === 'function' ) {
404+ this . collectAllNodes ( item , result ) ;
405+ }
406+ }
407+ } else if ( typeof subNode === 'object' && typeof subNode . getType === 'function' ) {
408+ this . collectAllNodes ( subNode , result ) ;
347409 }
348410 }
349411 }
0 commit comments