@@ -54,14 +54,22 @@ protected function annotateContent(string $path, string $content, array $annotat
5454
5555 $ needsPhpTag = $ phpOpenTagIndex === null || $ this ->needsPhpTag ($ file , $ phpOpenTagIndex );
5656
57- $ phpOpenTagIndex = $ this ->checkForDeclareStatement ($ file , $ phpOpenTagIndex );
57+ // Adjust position after declare statement for searching and adding new content
58+ $ phpOpenTagIndexAdjusted = $ this ->checkForDeclareStatement ($ file , $ phpOpenTagIndex );
5859
5960 $ docBlockCloseTagIndex = null ;
6061 if ($ needsPhpTag ) {
61- $ phpOpenTagIndex = null ;
62+ $ phpOpenTagIndexForSearch = null ;
63+ } else {
64+ $ phpOpenTagIndexForSearch = $ phpOpenTagIndexAdjusted ;
65+ }
66+ if ($ phpOpenTagIndexForSearch !== null ) {
67+ $ docBlockCloseTagIndex = $ this ->findExistingDocBlock ($ file , $ phpOpenTagIndexForSearch );
6268 }
63- if ($ phpOpenTagIndex !== null ) {
64- $ docBlockCloseTagIndex = $ this ->findExistingDocBlock ($ file , $ phpOpenTagIndex );
69+
70+ $ phpOpenTagIndex = $ phpOpenTagIndexAdjusted ;
71+ if ($ needsPhpTag ) {
72+ $ phpOpenTagIndex = null ;
6573 }
6674
6775 $ this ->resetCounter ();
@@ -170,7 +178,13 @@ protected function addNewTemplateDocBlock(File $file, array $annotations, ?int $
170178 if ($ phpOpenTagIndex === null ) {
171179 $ fixer ->addContentBefore (0 , $ docBlock );
172180 } else {
173- $ fixer ->addContent ($ phpOpenTagIndex , $ docBlock );
181+ // PHPCS v4 requires a blank line after <?php tag for PSR12 compliance
182+ // PHPCS v3 does not have this requirement
183+ if ($ this ->isV4 ()) {
184+ $ fixer ->addContent ($ phpOpenTagIndex , PHP_EOL . $ annotationString );
185+ } else {
186+ $ fixer ->addContent ($ phpOpenTagIndex , $ docBlock );
187+ }
174188 }
175189
176190 $ this ->_counter [static ::COUNT_ADDED ] = count ($ annotations );
@@ -534,6 +548,29 @@ protected function getVariableAnnotation(array $variable) {
534548 return $ annotation ;
535549 }
536550
551+ /**
552+ * @return bool
553+ */
554+ protected function isV4 (): bool {
555+ static $ isV4 = null ;
556+
557+ if ($ isV4 !== null ) {
558+ return $ isV4 ;
559+ }
560+
561+ // PHPCS v4+ requires blank line after <?php for PSR12 compliance
562+ // Check version from Config class constant
563+ if (class_exists (\PHP_CodeSniffer \Config::class)) {
564+ $ version = \PHP_CodeSniffer \Config::VERSION ;
565+ $ isV4 = version_compare ($ version , '4.0.0 ' , '>= ' );
566+ } else {
567+ // Fallback: assume v4+ if class doesn't exist
568+ $ isV4 = true ;
569+ }
570+
571+ return $ isV4 ;
572+ }
573+
537574 /**
538575 * @param \PHP_CodeSniffer\Files\File $file
539576 * @param int|null $phpOpenTagIndex
@@ -545,14 +582,20 @@ protected function checkForDeclareStatement(File $file, ?int $phpOpenTagIndex):
545582 return $ phpOpenTagIndex ;
546583 }
547584
548- $ nextIndex = $ file ->findNext (T_DECLARE , $ phpOpenTagIndex , $ phpOpenTagIndex + 2 );
549- if (!$ nextIndex ) {
585+ $ tokens = $ file ->getTokens ();
586+
587+ // Check if there's a declare statement right after the php open tag (on same or next line)
588+ $ nextNonWhitespace = $ file ->findNext (T_WHITESPACE , $ phpOpenTagIndex + 1 , null , true );
589+ if ($ nextNonWhitespace === false || $ tokens [$ nextNonWhitespace ]['code ' ] !== T_DECLARE ) {
550590 return $ phpOpenTagIndex ;
551591 }
552592
553- $ tokens = $ file ->getTokens ();
593+ // Check if declare is on the same line or immediately after php tag
594+ if ($ tokens [$ nextNonWhitespace ]['line ' ] > $ tokens [$ phpOpenTagIndex ]['line ' ] + 1 ) {
595+ return $ phpOpenTagIndex ;
596+ }
554597
555- $ lastIndexOfRow = $ tokens [$ nextIndex ]['parenthesis_closer ' ];
598+ $ lastIndexOfRow = $ tokens [$ nextNonWhitespace ]['parenthesis_closer ' ];
556599 while (!empty ($ tokens [$ lastIndexOfRow + 1 ]) && $ tokens [$ lastIndexOfRow + 1 ]['line ' ] === $ tokens [$ lastIndexOfRow ]['line ' ]) {
557600 $ lastIndexOfRow ++;
558601 }
0 commit comments