@@ -534,6 +534,96 @@ public function testFindInsertPositionWithAttribute(): void
534534 self ::assertSame (1 , $ result );
535535 }
536536
537+ public function testFindInsertPositionWithAttributeWithParameters (): void
538+ {
539+ $ code = "<?php #[AsEventListener(identifier: 'my-identifier')] class Foo {} " ;
540+ $ tokens = Tokens::fromCode ($ code );
541+
542+ $ method = new ReflectionMethod ($ this ->fixer , 'findInsertPosition ' );
543+
544+ // Find the class token index
545+ $ classIndex = null ;
546+ for ($ i = 0 ; $ i < $ tokens ->count (); ++$ i ) {
547+ if ($ tokens [$ i ]->isGivenKind (\T_CLASS )) {
548+ $ classIndex = $ i ;
549+ break ;
550+ }
551+ }
552+
553+ $ result = $ method ->invoke ($ this ->fixer , $ tokens , $ classIndex );
554+
555+ // Should return index 1 which is the #[ token
556+ self ::assertSame (1 , $ result );
557+ }
558+
559+ public function testApplyFixAddsDocBlockBeforeAttribute (): void
560+ {
561+ $ code = "<?php \n#[AsEventListener(identifier: 'my-identifier')] \nclass Foo {} " ;
562+ $ tokens = Tokens::fromCode ($ code );
563+ $ file = new SplFileInfo (__FILE__ );
564+
565+ $ method = new ReflectionMethod ($ this ->fixer , 'applyFix ' );
566+
567+ $ this ->fixer ->configure ([
568+ 'annotations ' => ['author ' => 'John Doe ' ],
569+ 'separate ' => 'none ' ,
570+ 'ensure_spacing ' => false ,
571+ ]);
572+ $ method ->invoke ($ this ->fixer , $ file , $ tokens );
573+
574+ $ result = $ tokens ->generateCode ();
575+
576+ // DocBlock should be BEFORE the attribute, not between attribute and class
577+ self ::assertMatchesRegularExpression ('/@author John Doe.*#\[AsEventListener/s ' , $ result );
578+ }
579+
580+ public function testFindInsertPositionWithMultipleAttributes (): void
581+ {
582+ $ code = "<?php \n#[Attribute1] \n#[Attribute2(param: 'value')] \nclass Foo {} " ;
583+ $ tokens = Tokens::fromCode ($ code );
584+
585+ $ method = new ReflectionMethod ($ this ->fixer , 'findInsertPosition ' );
586+
587+ // Find the class token index
588+ $ classIndex = null ;
589+ for ($ i = 0 ; $ i < $ tokens ->count (); ++$ i ) {
590+ if ($ tokens [$ i ]->isGivenKind (\T_CLASS )) {
591+ $ classIndex = $ i ;
592+ break ;
593+ }
594+ }
595+
596+ $ result = $ method ->invoke ($ this ->fixer , $ tokens , $ classIndex );
597+
598+ // Should return the index of the first attribute (#[Attribute1])
599+ // Token structure: [0]=T_OPEN_TAG, [1]=T_WHITESPACE, [2]=T_ATTRIBUTE(#[), ...
600+ // The first #[Attribute1] token is at index 2
601+ self ::assertTrue ($ tokens [$ result ]->isGivenKind (\T_ATTRIBUTE ));
602+ }
603+
604+ public function testFindExistingDocBlockWithAttributesBetween (): void
605+ {
606+ $ code = "<?php \n/** \n * @author John Doe \n */ \n#[SomeAttribute(param: 'value')] \nclass Foo {} " ;
607+ $ tokens = Tokens::fromCode ($ code );
608+
609+ $ method = new ReflectionMethod ($ this ->fixer , 'findExistingDocBlock ' );
610+
611+ // Find the class token index
612+ $ classIndex = null ;
613+ for ($ i = 0 ; $ i < $ tokens ->count (); ++$ i ) {
614+ if ($ tokens [$ i ]->isGivenKind (\T_CLASS )) {
615+ $ classIndex = $ i ;
616+ break ;
617+ }
618+ }
619+
620+ $ result = $ method ->invoke ($ this ->fixer , $ tokens , $ classIndex );
621+
622+ // Should find the DocBlock even with attribute in between
623+ self ::assertNotNull ($ result );
624+ self ::assertStringContainsString ('@author John Doe ' , $ tokens [$ result ]->getContent ());
625+ }
626+
537627 #[\PHPUnit \Framework \Attributes \RequiresPhp('>= 8.2 ' )]
538628 public function testFindInsertPositionWithReadonlyModifier (): void
539629 {
0 commit comments